package component
import (
"context"
"strconv"
"strings"
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat"
"github.com/projectdiscovery/retryablehttp-go"
urlutil "github.com/projectdiscovery/utils/url"
)
type Path struct {
value *Value
req *retryablehttp.Request
}
var _ Component = &Path{}
func NewPath() *Path {
return &Path{}
}
func (q *Path) Name() string {
return RequestPathComponent
}
func (q *Path) Parse(req *retryablehttp.Request) (bool, error) {
q.req = req
q.value = NewValue("")
splitted := strings.Split(req.Path, "/")
values := make(map[string]interface{})
for i, segment := range splitted {
if segment == "" && i == 0 {
continue
}
if segment == "" {
continue
}
key := strconv.Itoa(len(values) + 1)
values[key] = segment
}
q.value.SetParsed(dataformat.KVMap(values), "")
return true, nil
}
func (q *Path) Iterate(callback func(key string, value interface{}) error) (err error) {
q.value.parsed.Iterate(func(key string, value any) bool {
if errx := callback(key, value); errx != nil {
err = errx
return false
}
return true
})
return
}
func (q *Path) SetValue(key string, value string) error {
escaped := urlutil.PathEncode(value)
if !q.value.SetParsedValue(key, escaped) {
return ErrSetValue
}
return nil
}
func (q *Path) Delete(key string) error {
if !q.value.Delete(key) {
return ErrKeyNotFound
}
return nil
}
func (q *Path) Rebuild() (*retryablehttp.Request, error) {
originalSplitted := strings.Split(q.req.Path, "/")
rebuiltSegments := make([]string, 0, len(originalSplitted))
if len(originalSplitted) > 0 && originalSplitted[0] == "" {
rebuiltSegments = append(rebuiltSegments, "")
}
segmentIndex := 1
for i := 1; i < len(originalSplitted); i++ {
originalSegment := originalSplitted[i]
if originalSegment == "" {
continue
}
key := strconv.Itoa(segmentIndex)
if newValue, exists := q.value.parsed.Map.GetOrDefault(key, "").(string); exists && newValue != "" {
rebuiltSegments = append(rebuiltSegments, newValue)
} else {
rebuiltSegments = append(rebuiltSegments, originalSegment)
}
segmentIndex++
}
rebuiltPath := strings.Join(rebuiltSegments, "/")
if unescaped, err := urlutil.PathDecode(rebuiltPath); err == nil {
rebuiltPath = unescaped
}
cloned := q.req.Clone(context.Background())
if err := cloned.UpdateRelPath(rebuiltPath, true); err != nil {
cloned.RawPath = rebuiltPath
}
return cloned, nil
}
func (q *Path) Clone() Component {
return &Path{
value: q.value.Clone(),
req: q.req.Clone(context.Background()),
}
}