github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/http/utils.go (about)

     1  package http
     2  
     3  import (
     4  	"bytes"
     5  	"compress/gzip"
     6  	"compress/zlib"
     7  	"io"
     8  	"net/http"
     9  	"net/http/httputil"
    10  	"strings"
    11  
    12  	"github.com/pkg/errors"
    13  	"golang.org/x/text/encoding/simplifiedchinese"
    14  	"golang.org/x/text/transform"
    15  
    16  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
    17  	"github.com/projectdiscovery/nuclei/v2/pkg/types"
    18  	"github.com/projectdiscovery/rawhttp"
    19  	mapsutil "github.com/projectdiscovery/utils/maps"
    20  	stringsutil "github.com/projectdiscovery/utils/strings"
    21  )
    22  
    23  type redirectedResponse struct {
    24  	headers      []byte
    25  	body         []byte
    26  	fullResponse []byte
    27  	resp         *http.Response
    28  }
    29  
    30  // dumpResponseWithRedirectChain dumps a http response with the
    31  // complete http redirect chain.
    32  //
    33  // It preserves the order in which responses were given to requests
    34  // and returns the data to the user for matching and viewing in that order.
    35  //
    36  // Inspired from - https://github.com/ffuf/ffuf/issues/324#issuecomment-719858923
    37  func dumpResponseWithRedirectChain(resp *http.Response, body []byte) ([]redirectedResponse, error) {
    38  	var response []redirectedResponse
    39  
    40  	respData, err := httputil.DumpResponse(resp, false)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	respObj := redirectedResponse{
    45  		headers:      respData,
    46  		body:         body,
    47  		resp:         resp,
    48  		fullResponse: bytes.Join([][]byte{respData, body}, []byte{}),
    49  	}
    50  	if err := normalizeResponseBody(resp, &respObj); err != nil {
    51  		return nil, err
    52  	}
    53  	response = append(response, respObj)
    54  
    55  	var redirectResp *http.Response
    56  	if resp != nil && resp.Request != nil {
    57  		redirectResp = resp.Request.Response
    58  	}
    59  	for redirectResp != nil {
    60  		var body []byte
    61  
    62  		respData, err := httputil.DumpResponse(redirectResp, false)
    63  		if err != nil {
    64  			break
    65  		}
    66  		if redirectResp.Body != nil {
    67  			body, _ = io.ReadAll(redirectResp.Body)
    68  		}
    69  		respObj := redirectedResponse{
    70  			headers:      respData,
    71  			body:         body,
    72  			resp:         redirectResp,
    73  			fullResponse: bytes.Join([][]byte{respData, body}, []byte{}),
    74  		}
    75  		if err := normalizeResponseBody(redirectResp, &respObj); err != nil {
    76  			return nil, err
    77  		}
    78  		response = append(response, respObj)
    79  		redirectResp = redirectResp.Request.Response
    80  	}
    81  	return response, nil
    82  }
    83  
    84  // normalizeResponseBody performs normalization on the http response object.
    85  func normalizeResponseBody(resp *http.Response, response *redirectedResponse) error {
    86  	var err error
    87  	// net/http doesn't automatically decompress the response body if an
    88  	// encoding has been specified by the user in the request so in case we have to
    89  	// manually do it.
    90  	dataOrig := response.body
    91  	response.body, err = handleDecompression(resp, response.body)
    92  	// in case of error use original data
    93  	if err != nil {
    94  		response.body = dataOrig
    95  	}
    96  	response.fullResponse = bytes.ReplaceAll(response.fullResponse, dataOrig, response.body)
    97  
    98  	// Decode gbk response content-types
    99  	// gb18030 supersedes gb2312
   100  	responseContentType := resp.Header.Get("Content-Type")
   101  	if isContentTypeGbk(responseContentType) {
   102  		response.fullResponse, err = decodeGBK(response.fullResponse)
   103  		if err != nil {
   104  			return errors.Wrap(err, "could not gbk decode")
   105  		}
   106  
   107  		// the uncompressed body needs to be decoded to standard utf8
   108  		response.body, err = decodeGBK(response.body)
   109  		if err != nil {
   110  			return errors.Wrap(err, "could not gbk decode")
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  // dump creates a dump of the http request in form of a byte slice
   117  func dump(req *generatedRequest, reqURL string) ([]byte, error) {
   118  	if req.request != nil {
   119  		return req.request.Dump()
   120  	}
   121  	rawHttpOptions := &rawhttp.Options{CustomHeaders: req.rawRequest.UnsafeHeaders, CustomRawBytes: req.rawRequest.UnsafeRawBytes}
   122  	return rawhttp.DumpRequestRaw(req.rawRequest.Method, reqURL, req.rawRequest.Path, generators.ExpandMapValues(req.rawRequest.Headers), io.NopCloser(strings.NewReader(req.rawRequest.Data)), rawHttpOptions)
   123  }
   124  
   125  // handleDecompression if the user specified a custom encoding (as golang transport doesn't do this automatically)
   126  func handleDecompression(resp *http.Response, bodyOrig []byte) (bodyDec []byte, err error) {
   127  	if resp == nil {
   128  		return bodyOrig, nil
   129  	}
   130  
   131  	var reader io.ReadCloser
   132  	switch resp.Header.Get("Content-Encoding") {
   133  	case "gzip":
   134  		reader, err = gzip.NewReader(bytes.NewReader(bodyOrig))
   135  	case "deflate":
   136  		reader, err = zlib.NewReader(bytes.NewReader(bodyOrig))
   137  	default:
   138  		return bodyOrig, nil
   139  	}
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	defer reader.Close()
   144  
   145  	bodyDec, err = io.ReadAll(reader)
   146  	if err != nil {
   147  		return bodyOrig, err
   148  	}
   149  	return bodyDec, nil
   150  }
   151  
   152  // decodeGBK converts GBK to UTF-8
   153  func decodeGBK(s []byte) ([]byte, error) {
   154  	I := bytes.NewReader(s)
   155  	O := transform.NewReader(I, simplifiedchinese.GBK.NewDecoder())
   156  	d, e := io.ReadAll(O)
   157  	if e != nil {
   158  		return nil, e
   159  	}
   160  	return d, nil
   161  }
   162  
   163  // isContentTypeGbk checks if the content-type header is gbk
   164  func isContentTypeGbk(contentType string) bool {
   165  	contentType = strings.ToLower(contentType)
   166  	return stringsutil.ContainsAny(contentType, "gbk", "gb2312", "gb18030")
   167  }
   168  
   169  // if template contains more than 1 request and matchers require requestcondition from
   170  // both requests , then we need to request for event from interactsh even if current request
   171  // doesnot use interactsh url in it
   172  func getInteractshURLsFromEvent(event map[string]interface{}) []string {
   173  	interactshUrls := map[string]struct{}{}
   174  	for k, v := range event {
   175  		if strings.HasPrefix(k, "interactsh-url") {
   176  			interactshUrls[types.ToString(v)] = struct{}{}
   177  		}
   178  	}
   179  	return mapsutil.GetKeys(interactshUrls)
   180  }