github.com/gospider007/requests@v0.0.0-20240506025355-c73d46169a23/tools.go (about)

     1  package requests
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"net/http"
    11  	"net/textproto"
    12  	"net/url"
    13  	"strconv"
    14  	"strings"
    15  	_ "unsafe"
    16  
    17  	"github.com/gospider007/ja3"
    18  	"golang.org/x/exp/slices"
    19  	"golang.org/x/net/http/httpguts"
    20  )
    21  
    22  func getHost(req *http.Request) string {
    23  	host := req.Host
    24  	if host == "" {
    25  		host = req.URL.Host
    26  	}
    27  	_, port, _ := net.SplitHostPort(host)
    28  	if port == "" {
    29  		if req.URL.Scheme == "https" {
    30  			port = "443"
    31  		} else {
    32  			port = "80"
    33  		}
    34  		return fmt.Sprintf("%s:%s", host, port)
    35  	}
    36  	return host
    37  }
    38  func getAddr(uurl *url.URL) (addr string) {
    39  	if uurl == nil {
    40  		return ""
    41  	}
    42  	_, port, _ := net.SplitHostPort(uurl.Host)
    43  	if port == "" {
    44  		if uurl.Scheme == "https" {
    45  			port = "443"
    46  		} else {
    47  			port = "80"
    48  		}
    49  		return fmt.Sprintf("%s:%s", uurl.Host, port)
    50  	}
    51  	return uurl.Host
    52  }
    53  func cloneUrl(u *url.URL) *url.URL {
    54  	if u == nil {
    55  		return nil
    56  	}
    57  	r := *u
    58  	return &r
    59  }
    60  
    61  var replaceMap = map[string]string{
    62  	"Sec-Ch-Ua":          "sec-ch-ua",
    63  	"Sec-Ch-Ua-Mobile":   "sec-ch-ua-mobile",
    64  	"Sec-Ch-Ua-Platform": "sec-ch-ua-platform",
    65  }
    66  
    67  //go:linkname escapeQuotes mime/multipart.escapeQuotes
    68  func escapeQuotes(string) string
    69  
    70  //go:linkname readCookies net/http.readCookies
    71  func readCookies(h http.Header, filter string) []*http.Cookie
    72  
    73  //go:linkname readSetCookies net/http.readSetCookies
    74  func readSetCookies(h http.Header) []*http.Cookie
    75  
    76  //go:linkname ReadRequest net/http.readRequest
    77  func ReadRequest(b *bufio.Reader) (*http.Request, error)
    78  
    79  //go:linkname removeZone net/http.removeZone
    80  func removeZone(host string) string
    81  
    82  //go:linkname shouldSendContentLength net/http.(*transferWriter).shouldSendContentLength
    83  func shouldSendContentLength(t *http.Request) bool
    84  
    85  //go:linkname removeEmptyPort net/http.removeEmptyPort
    86  func removeEmptyPort(host string) string
    87  
    88  //go:linkname redirectBehavior net/http.redirectBehavior
    89  func redirectBehavior(reqMethod string, resp *http.Response, ireq *http.Request) (redirectMethod string, shouldRedirect, includeBody bool)
    90  
    91  //go:linkname readTransfer net/http.readTransfer
    92  func readTransfer(msg any, r *bufio.Reader) (err error)
    93  
    94  var filterHeaderKeys = ja3.DefaultOrderHeadersWithH2()
    95  
    96  func httpWrite(r *http.Request, w *bufio.Writer, orderHeaders []string) (err error) {
    97  	for i := range orderHeaders {
    98  		orderHeaders[i] = textproto.CanonicalMIMEHeaderKey(orderHeaders[i])
    99  	}
   100  	host := r.Host
   101  	if host == "" {
   102  		host = r.URL.Host
   103  	}
   104  	host, err = httpguts.PunycodeHostPort(host)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	host = removeZone(host)
   109  	ruri := r.URL.RequestURI()
   110  	if r.Method == "CONNECT" && r.URL.Path == "" {
   111  		if r.URL.Opaque != "" {
   112  			ruri = r.URL.Opaque
   113  		} else {
   114  			ruri = host
   115  		}
   116  	}
   117  	if r.Header.Get("Host") == "" {
   118  		r.Header.Set("Host", host)
   119  	}
   120  	if r.Header.Get("Connection") == "" {
   121  		r.Header.Set("Connection", "keep-alive")
   122  	}
   123  	if r.Header.Get("User-Agent") == "" {
   124  		r.Header.Set("User-Agent", UserAgent)
   125  	}
   126  	if r.Header.Get("Content-Length") == "" && r.ContentLength != 0 && shouldSendContentLength(r) {
   127  		r.Header.Set("Content-Length", fmt.Sprint(r.ContentLength))
   128  	}
   129  	if _, err = w.WriteString(fmt.Sprintf("%s %s %s\r\n", r.Method, ruri, r.Proto)); err != nil {
   130  		return err
   131  	}
   132  	for _, k := range orderHeaders {
   133  		if vs, ok := r.Header[k]; ok {
   134  			if k2, ok := replaceMap[k]; ok {
   135  				k = k2
   136  			}
   137  			if slices.Contains(filterHeaderKeys, k) {
   138  				continue
   139  			}
   140  			for _, v := range vs {
   141  				if _, err = w.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)); err != nil {
   142  					return err
   143  				}
   144  			}
   145  		}
   146  	}
   147  	for k, vs := range r.Header {
   148  		if !slices.Contains(orderHeaders, k) {
   149  			if k2, ok := replaceMap[k]; ok {
   150  				k = k2
   151  			}
   152  			if slices.Contains(filterHeaderKeys, k) {
   153  				continue
   154  			}
   155  			for _, v := range vs {
   156  				if _, err = w.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)); err != nil {
   157  					return err
   158  				}
   159  			}
   160  		}
   161  	}
   162  	if _, err = w.WriteString("\r\n"); err != nil {
   163  		return err
   164  	}
   165  	if r.Body != nil {
   166  		if _, err = io.Copy(w, r.Body); err != nil {
   167  			return err
   168  		}
   169  	}
   170  	return w.Flush()
   171  }
   172  func NewRequestWithContext(ctx context.Context, method string, u *url.URL, body io.Reader) (*http.Request, error) {
   173  	req := (&http.Request{}).WithContext(ctx)
   174  	if method == "" {
   175  		req.Method = http.MethodGet
   176  	} else {
   177  		req.Method = strings.ToUpper(method)
   178  	}
   179  	req.URL = u
   180  	req.Proto = "HTTP/1.1"
   181  	req.ProtoMajor = 1
   182  	req.ProtoMinor = 1
   183  	req.Host = u.Host
   184  	u.Host = removeEmptyPort(u.Host)
   185  	if body != nil {
   186  		if v, ok := body.(interface{ Len() int }); ok {
   187  			req.ContentLength = int64(v.Len())
   188  		}
   189  		rc, ok := body.(io.ReadCloser)
   190  		if !ok {
   191  			rc = io.NopCloser(body)
   192  		}
   193  		req.Body = rc
   194  	}
   195  	return req, nil
   196  }
   197  
   198  func readResponse(tp *textproto.Reader, req *http.Request) (*http.Response, error) {
   199  	resp := &http.Response{
   200  		Request: req,
   201  	}
   202  	// Parse the first line of the response.
   203  	line, err := tp.ReadLine()
   204  	if err != nil {
   205  		if err == io.EOF {
   206  			err = io.ErrUnexpectedEOF
   207  		}
   208  		return nil, err
   209  	}
   210  	proto, status, ok := strings.Cut(line, " ")
   211  	if !ok {
   212  		return nil, errors.New("malformed HTTP response")
   213  	}
   214  	resp.Proto = proto
   215  	resp.Status = strings.TrimLeft(status, " ")
   216  	statusCode, _, _ := strings.Cut(resp.Status, " ")
   217  	if resp.StatusCode, err = strconv.Atoi(statusCode); err != nil {
   218  		return nil, errors.New("malformed HTTP status code")
   219  	}
   220  	if resp.ProtoMajor, resp.ProtoMinor, ok = http.ParseHTTPVersion(resp.Proto); !ok {
   221  		return nil, errors.New("malformed HTTP version")
   222  	}
   223  	// Parse the response headers.
   224  	mimeHeader, err := tp.ReadMIMEHeader()
   225  	if err != nil {
   226  		if err == io.EOF {
   227  			err = io.ErrUnexpectedEOF
   228  		}
   229  		return nil, err
   230  	}
   231  	resp.Header = http.Header(mimeHeader)
   232  	return resp, readTransfer(resp, tp.R)
   233  }
   234  
   235  func addCookie(req *http.Request, cookies Cookies) {
   236  	cooks := Cookies(readCookies(req.Header, ""))
   237  	for _, cook := range cookies {
   238  		if val := cooks.Get(cook.Name); val == nil {
   239  			cooks = cooks.append(cook)
   240  		}
   241  	}
   242  	if result := cooks.String(); result != "" {
   243  		req.Header.Set("Cookie", result)
   244  	}
   245  }