package urlutil import ( "bytes" "strings" mapsutil "github.com/projectdiscovery/utils/maps" ) // Only difference between OrderedParams and Params is that // OrderedParams preserves order of parameters everythign else is same // OrderedParams is a map that preserves the order of elements type OrderedParams struct { om mapsutil.OrderedMap[string, []string] // IncludeEquals is used to include = in encoded parameters, default is false IncludeEquals bool } // NewOrderedParams creates a new ordered params func NewOrderedParams() *OrderedParams { return &OrderedParams{ om: mapsutil.NewOrderedMap[string, []string](), } } // IsEmpty checks if the OrderedParams is empty func (o *OrderedParams) IsEmpty() bool { return o.om.IsEmpty() } // Update is similar to Set but it takes value as slice (similar to internal implementation of url.Values) func (o *OrderedParams) Update(key string, value []string) { o.om.Set(key, value) } // Iterate iterates over the OrderedParams func (o *OrderedParams) Iterate(f func(key string, value []string) bool) { o.om.Iterate(func(key string, value []string) bool { return f(key, value) }) } // Add Parameters to store func (o *OrderedParams) Add(key string, value ...string) { if arr, ok := o.om.Get(key); ok && len(arr) > 0 { if len(value) != 0 { o.om.Set(key, append(arr, value...)) } } else { o.om.Set(key, value) } } // Set sets the key to value and replaces if already exists func (o *OrderedParams) Set(key string, value string) { o.om.Set(key, []string{value}) } // Get returns first value of given key func (o *OrderedParams) Get(key string) string { val, ok := o.om.Get(key) if !ok || len(val) == 0 { return "" } return val[0] } // GetAll returns all values of given key or returns empty slice if key doesn't exist func (o *OrderedParams) GetAll(key string) []string { val, ok := o.om.Get(key) if !ok || len(val) == 0 { return []string{} } return val } // Has returns if given key exists func (o *OrderedParams) Has(key string) bool { return o.om.Has(key) } // Del deletes values associated with key func (o *OrderedParams) Del(key string) { o.om.Delete(key) } // Merges given paramset into existing one with base as priority func (o *OrderedParams) Merge(raw string) { o.Decode(raw) } // Encode returns encoded parameters by preserving order func (o *OrderedParams) Encode() string { if o.om.IsEmpty() { return "" } var buf strings.Builder for _, k := range o.om.GetKeys() { vs, _ := o.om.Get(k) keyEscaped := ParamEncode(k) for _, v := range vs { if buf.Len() > 0 { buf.WriteByte('&') } buf.WriteString(keyEscaped) value := ParamEncode(v) //donot specify = if parameter has no value (reference: nuclei-templates) if o.IncludeEquals || value != "" { buf.WriteRune('=') buf.WriteString(value) } } } return buf.String() } // Decode is opposite of Encode() where ("bar=baz&foo=quux") is parsed // Parameters are loosely parsed to allow any scenario func (o *OrderedParams) Decode(raw string) { if o.om.Len() == 0 { o.om = mapsutil.NewOrderedMap[string, []string]() } arr := []string{} var tbuff bytes.Buffer for _, v := range raw { switch v { case '&': arr = append(arr, tbuff.String()) tbuff.Reset() case ';': if AllowLegacySeperator { arr = append(arr, tbuff.String()) tbuff.Reset() continue } tbuff.WriteRune(v) default: tbuff.WriteRune(v) } } if tbuff.Len() > 0 { arr = append(arr, tbuff.String()) } for _, pair := range arr { d := strings.SplitN(pair, "=", 2) if len(d) == 2 { o.Add(d[0], d[1]) } else if len(d) == 1 { o.Add(d[0], "") } } } // Clone returns a copy of the ordered params func (o *OrderedParams) Clone() *OrderedParams { clone := NewOrderedParams() o.om.Iterate(func(key string, value []string) bool { // this needs to be a deep copy (from reference in nuclei race condition issue) if len(value) != 0 { clone.Add(key, value...) } else { clone.Add(key, "") } return true }) return clone }