/ pkg / httpx / httputil.go
httputil.go
  1  // Copyright (c) 2024-2026 Tencent Zhuque Lab. All rights reserved.
  2  //
  3  // Licensed under the Apache License, Version 2.0 (the "License");
  4  // you may not use this file except in compliance with the License.
  5  // You may obtain a copy of the License at
  6  //
  7  //     http://www.apache.org/licenses/LICENSE-2.0
  8  //
  9  // Unless required by applicable law or agreed to in writing, software
 10  // distributed under the License is distributed on an "AS IS" BASIS,
 11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  // See the License for the specific language governing permissions and
 13  // limitations under the License.
 14  //
 15  // Requirement: Any integration or derivative work must explicitly attribute
 16  // Tencent Zhuque Lab (https://github.com/Tencent/AI-Infra-Guard) in its
 17  // documentation or user interface, as detailed in the NOTICE file.
 18  
 19  // Package httpx http相关
 20  package httpx
 21  
 22  import (
 23  	"bufio"
 24  	"bytes"
 25  	"fmt"
 26  	"io"
 27  	"io/ioutil"
 28  	"net/http"
 29  	"net/http/httputil"
 30  	"net/url"
 31  	"strings"
 32  
 33  	"github.com/projectdiscovery/rawhttp/client"
 34  
 35  	"github.com/projectdiscovery/retryablehttp-go"
 36  )
 37  
 38  const (
 39  	headerParts  = 2
 40  	requestParts = 3
 41  )
 42  const (
 43  	// HTTP defines the plain http scheme
 44  	HTTP = "http"
 45  	// HTTPS defines the secure http scheme
 46  	HTTPS = "https"
 47  	// HTTPorHTTPS defines the both http and https scheme
 48  	HTTPorHTTPS = "http|https"
 49  )
 50  
 51  // DumpRequest to string
 52  func DumpRequest(req *retryablehttp.Request) (string, error) {
 53  	dump, err := httputil.DumpRequestOut(req.Request, true)
 54  
 55  	return string(dump), err
 56  }
 57  
 58  // DumpResponse to string
 59  func DumpResponse(resp *http.Response) (string, error) {
 60  	// httputil.DumpResponse does not work with websockets
 61  	if resp.StatusCode == http.StatusContinue {
 62  		raw := resp.Status + "\n"
 63  		for h, v := range resp.Header {
 64  			raw += fmt.Sprintf("%s: %s\n", h, v)
 65  		}
 66  		return raw, nil
 67  	}
 68  
 69  	raw, err := httputil.DumpResponse(resp, true)
 70  	return string(raw), err
 71  }
 72  
 73  // DumpRequestRaw 导出请求包原文
 74  func DumpRequestRaw(req *retryablehttp.Request) string {
 75  	b := strings.Builder{}
 76  
 77  	b.WriteString(fmt.Sprintf("%s %s"+client.NewLine, req.Method, req.URL))
 78  
 79  	for k, v := range req.Header {
 80  		value := strings.Join(v, " ")
 81  		if value != "" {
 82  			b.WriteString(fmt.Sprintf("%s:%s"+client.NewLine, k, value))
 83  		} else {
 84  			b.WriteString(fmt.Sprintf("%s"+client.NewLine, k))
 85  		}
 86  	}
 87  
 88  	b.WriteString(client.NewLine)
 89  	body, err := req.BodyBytes()
 90  	if err != nil {
 91  		body = nil
 92  	}
 93  	if body != nil {
 94  		var buf bytes.Buffer
 95  		tee := io.TeeReader(req.Body, &buf)
 96  		body, err := ioutil.ReadAll(tee)
 97  		if err != nil {
 98  			return b.String()
 99  		}
100  		b.Write(body)
101  
102  	}
103  	return b.String()
104  }
105  
106  // ParseRequest from raw string
107  func ParseRequest(req string, unsafe bool) (method, path string, headers map[string]string, body string, err error) {
108  	headers = make(map[string]string)
109  	reader := bufio.NewReader(strings.NewReader(req))
110  	s, err := reader.ReadString('\n')
111  	if err != nil {
112  		err = fmt.Errorf("could not read request: %s", err)
113  		return
114  	}
115  	parts := strings.Split(s, " ")
116  	if len(parts) < requestParts {
117  		err = fmt.Errorf("malformed request supplied")
118  		return
119  	}
120  	method = parts[0]
121  
122  	for {
123  		line, readErr := reader.ReadString('\n')
124  		line = strings.TrimSpace(line)
125  
126  		if readErr != nil || line == "" {
127  			break
128  		}
129  
130  		// Unsafe skips all checks
131  		p := strings.SplitN(line, ":", headerParts)
132  		key := p[0]
133  		value := ""
134  		if len(p) == headerParts {
135  			value = p[1]
136  		}
137  
138  		if !unsafe {
139  			if len(p) != headerParts {
140  				continue
141  			}
142  
143  			if strings.EqualFold(key, "content-length") {
144  				continue
145  			}
146  
147  			key = strings.TrimSpace(key)
148  			value = strings.TrimSpace(value)
149  		}
150  
151  		headers[key] = value
152  	}
153  
154  	// Handle case with the full http url in path. In that case,
155  	// ignore any host header that we encounter and use the path as request URL
156  	if strings.HasPrefix(parts[1], "http") {
157  		var parsed *url.URL
158  		parsed, err = url.Parse(parts[1])
159  		if err != nil {
160  			err = fmt.Errorf("could not parse request URL: %s", err)
161  			return
162  		}
163  		path = parts[1]
164  		headers["Host"] = parsed.Host
165  	} else {
166  		path = parts[1]
167  	}
168  
169  	// Set the request body
170  	b, err := ioutil.ReadAll(reader)
171  	if err != nil {
172  		err = fmt.Errorf("could not read request body: %s", err)
173  		return
174  	}
175  	body = string(b)
176  
177  	return method, path, headers, body, nil
178  }