package dataformat
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/projectdiscovery/gologger"
mapsutil "github.com/projectdiscovery/utils/maps"
urlutil "github.com/projectdiscovery/utils/url"
)
const (
normalizedRegex = `_(\d+)$`
)
var (
reNormalized = regexp.MustCompile(normalizedRegex)
)
type Form struct{}
var (
_ DataFormat = &Form{}
)
func NewForm() *Form {
return &Form{}
}
func (f *Form) IsType(data string) bool {
return false
}
func (f *Form) Encode(data KV) (string, error) {
params := urlutil.NewOrderedParams()
data.Iterate(func(key string, value any) bool {
params.Add(key, fmt.Sprint(value))
return true
})
normalized := map[string]map[string]string{}
for _, origKey := range data.OrderedMap.GetKeys() {
if origKey != "" && !reNormalized.MatchString(origKey) {
params.Iterate(func(key string, value []string) bool {
if strings.HasPrefix(key, origKey) && reNormalized.MatchString(key) {
m := map[string]string{}
if normalized[origKey] != nil {
m = normalized[origKey]
}
if len(value) == 1 {
m[key] = value[0]
} else {
m[key] = ""
}
normalized[origKey] = m
params.Del(key)
}
return true
})
}
}
if len(normalized) > 0 {
for k, v := range normalized {
maxIndex := -1
for key := range v {
matches := reNormalized.FindStringSubmatch(key)
if len(matches) == 2 {
dataIdx, err := strconv.Atoi(matches[1])
if err != nil {
gologger.Verbose().Msgf("error converting normalized index(%v) to integer: %v", matches[1], err)
continue
}
if dataIdx > maxIndex {
maxIndex = dataIdx
}
}
}
if maxIndex >= 0 {
data := make([]string, maxIndex+1)
for key, value := range v {
matches := reNormalized.FindStringSubmatch(key)
if len(matches) == 2 {
dataIdx, err := strconv.Atoi(matches[1])
if err != nil {
gologger.Verbose().Msgf("error converting data index to integer: %v", err)
continue
}
if dataIdx > 0 && dataIdx <= len(data) {
data[dataIdx-1] = value
} else {
gologger.Verbose().Msgf("data index out of range: %d", dataIdx)
}
}
}
if len(params.Get(k)) > 0 {
data[maxIndex] = fmt.Sprint(params.Get(k))
}
params.Del(k)
if len(data) > 0 {
params.Add(k, data...)
}
}
}
}
encoded := params.Encode()
return encoded, nil
}
func (f *Form) Decode(data string) (KV, error) {
ordered_params := urlutil.NewOrderedParams()
ordered_params.Merge(data)
values := mapsutil.NewOrderedMap[string, any]()
ordered_params.Iterate(func(key string, value []string) bool {
if len(value) == 1 {
values.Set(key, value[0])
} else {
for i := 0; i < len(value)-1; i++ {
values.Set(key+"_"+strconv.Itoa(i+1), value[i])
}
values.Set(key, value[len(value)-1])
}
return true
})
return KVOrderedMap(&values), nil
}
func (f *Form) Name() string {
return FormDataFormat
}