mirror of https://github.com/daffainfo/nuclei.git
151 lines
3.8 KiB
Go
151 lines
3.8 KiB
Go
package component
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/projectdiscovery/gologger"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat"
|
|
"github.com/projectdiscovery/retryablehttp-go"
|
|
readerutil "github.com/projectdiscovery/utils/reader"
|
|
)
|
|
|
|
// Body is a component for a request body
|
|
type Body struct {
|
|
value *Value
|
|
|
|
req *retryablehttp.Request
|
|
}
|
|
|
|
var _ Component = &Body{}
|
|
|
|
// NewBody creates a new body component
|
|
func NewBody() *Body {
|
|
return &Body{}
|
|
}
|
|
|
|
// Name returns the name of the component
|
|
func (b *Body) Name() string {
|
|
return RequestBodyComponent
|
|
}
|
|
|
|
// Parse parses the component and returns the
|
|
// parsed component
|
|
func (b *Body) Parse(req *retryablehttp.Request) (bool, error) {
|
|
if req.Body == nil {
|
|
return false, nil
|
|
}
|
|
b.req = req
|
|
|
|
contentType := req.Header.Get("Content-Type")
|
|
|
|
data, err := io.ReadAll(req.Body)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "could not read body")
|
|
}
|
|
req.Body = io.NopCloser(bytes.NewReader(data))
|
|
dataStr := string(data)
|
|
|
|
if dataStr == "" {
|
|
return false, nil
|
|
}
|
|
|
|
b.value = NewValue(dataStr)
|
|
tmp := b.value.Parsed()
|
|
if !tmp.IsNIL() {
|
|
return true, nil
|
|
}
|
|
|
|
switch {
|
|
case strings.Contains(contentType, "application/json") && tmp.IsNIL():
|
|
return b.parseBody(dataformat.JSONDataFormat, req)
|
|
case strings.Contains(contentType, "application/xml") && tmp.IsNIL():
|
|
return b.parseBody(dataformat.XMLDataFormat, req)
|
|
case strings.Contains(contentType, "multipart/form-data") && tmp.IsNIL():
|
|
return b.parseBody(dataformat.MultiPartFormDataFormat, req)
|
|
}
|
|
parsed, err := b.parseBody(dataformat.FormDataFormat, req)
|
|
if err != nil {
|
|
gologger.Warning().Msgf("Could not parse body as form data: %s\n", err)
|
|
return b.parseBody(dataformat.RawDataFormat, req)
|
|
}
|
|
return parsed, err
|
|
}
|
|
|
|
// parseBody parses a body with a custom decoder
|
|
func (b *Body) parseBody(decoderName string, req *retryablehttp.Request) (bool, error) {
|
|
decoder := dataformat.Get(decoderName)
|
|
if decoderName == dataformat.MultiPartFormDataFormat {
|
|
// set content type to extract boundary
|
|
if err := decoder.(*dataformat.MultiPartForm).ParseBoundary(req.Header.Get("Content-Type")); err != nil {
|
|
return false, errors.Wrap(err, "could not parse boundary")
|
|
}
|
|
}
|
|
decoded, err := decoder.Decode(b.value.String())
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "could not decode raw")
|
|
}
|
|
b.value.SetParsed(decoded, decoder.Name())
|
|
return true, nil
|
|
}
|
|
|
|
// Iterate iterates through the component
|
|
func (b *Body) Iterate(callback func(key string, value interface{}) error) (errx error) {
|
|
b.value.parsed.Iterate(func(key string, value any) bool {
|
|
if strings.HasPrefix(key, "#_") {
|
|
return true
|
|
}
|
|
if err := callback(key, value); err != nil {
|
|
errx = err
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
return
|
|
}
|
|
|
|
// SetValue sets a value in the component
|
|
func (b *Body) SetValue(key string, value string) error {
|
|
if !b.value.SetParsedValue(key, value) {
|
|
return ErrSetValue
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Delete deletes a key from the component
|
|
func (b *Body) Delete(key string) error {
|
|
if !b.value.Delete(key) {
|
|
return ErrKeyNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Rebuild returns a new request with the
|
|
// component rebuilt
|
|
func (b *Body) Rebuild() (*retryablehttp.Request, error) {
|
|
encoded, err := b.value.Encode()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not encode body")
|
|
}
|
|
cloned := b.req.Clone(context.Background())
|
|
reusableReader, err := readerutil.NewReusableReadCloser(encoded)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not create reusable reader")
|
|
}
|
|
cloned.Body = reusableReader
|
|
cloned.ContentLength = int64(len(encoded))
|
|
cloned.Header.Set("Content-Length", strconv.Itoa(len(encoded)))
|
|
return cloned, nil
|
|
}
|
|
|
|
func (b *Body) Clone() Component {
|
|
return &Body{
|
|
value: b.value.Clone(),
|
|
req: b.req.Clone(context.Background()),
|
|
}
|
|
}
|