|
|
|
|
|
package httpx |
|
|
|
|
|
import ( |
|
|
"bufio" |
|
|
"bytes" |
|
|
"fmt" |
|
|
"io" |
|
|
"io/ioutil" |
|
|
"net/http" |
|
|
"net/http/httputil" |
|
|
"net/url" |
|
|
"strings" |
|
|
|
|
|
"github.com/projectdiscovery/rawhttp/client" |
|
|
|
|
|
"github.com/projectdiscovery/retryablehttp-go" |
|
|
) |
|
|
|
|
|
const ( |
|
|
headerParts = 2 |
|
|
requestParts = 3 |
|
|
) |
|
|
const ( |
|
|
|
|
|
HTTP = "http" |
|
|
|
|
|
HTTPS = "https" |
|
|
|
|
|
HTTPorHTTPS = "http|https" |
|
|
) |
|
|
|
|
|
|
|
|
func DumpRequest(req *retryablehttp.Request) (string, error) { |
|
|
dump, err := httputil.DumpRequestOut(req.Request, true) |
|
|
|
|
|
return string(dump), err |
|
|
} |
|
|
|
|
|
|
|
|
func DumpResponse(resp *http.Response) (string, error) { |
|
|
|
|
|
if resp.StatusCode == http.StatusContinue { |
|
|
raw := resp.Status + "\n" |
|
|
for h, v := range resp.Header { |
|
|
raw += fmt.Sprintf("%s: %s\n", h, v) |
|
|
} |
|
|
return raw, nil |
|
|
} |
|
|
|
|
|
raw, err := httputil.DumpResponse(resp, true) |
|
|
return string(raw), err |
|
|
} |
|
|
|
|
|
|
|
|
func DumpRequestRaw(req *retryablehttp.Request) string { |
|
|
b := strings.Builder{} |
|
|
|
|
|
b.WriteString(fmt.Sprintf("%s %s"+client.NewLine, req.Method, req.URL)) |
|
|
|
|
|
for k, v := range req.Header { |
|
|
value := strings.Join(v, " ") |
|
|
if value != "" { |
|
|
b.WriteString(fmt.Sprintf("%s:%s"+client.NewLine, k, value)) |
|
|
} else { |
|
|
b.WriteString(fmt.Sprintf("%s"+client.NewLine, k)) |
|
|
} |
|
|
} |
|
|
|
|
|
b.WriteString(client.NewLine) |
|
|
body, err := req.BodyBytes() |
|
|
if err != nil { |
|
|
body = nil |
|
|
} |
|
|
if body != nil { |
|
|
var buf bytes.Buffer |
|
|
tee := io.TeeReader(req.Body, &buf) |
|
|
body, err := ioutil.ReadAll(tee) |
|
|
if err != nil { |
|
|
return b.String() |
|
|
} |
|
|
b.Write(body) |
|
|
|
|
|
} |
|
|
return b.String() |
|
|
} |
|
|
|
|
|
|
|
|
func ParseRequest(req string, unsafe bool) (method, path string, headers map[string]string, body string, err error) { |
|
|
headers = make(map[string]string) |
|
|
reader := bufio.NewReader(strings.NewReader(req)) |
|
|
s, err := reader.ReadString('\n') |
|
|
if err != nil { |
|
|
err = fmt.Errorf("could not read request: %s", err) |
|
|
return |
|
|
} |
|
|
parts := strings.Split(s, " ") |
|
|
if len(parts) < requestParts { |
|
|
err = fmt.Errorf("malformed request supplied") |
|
|
return |
|
|
} |
|
|
method = parts[0] |
|
|
|
|
|
for { |
|
|
line, readErr := reader.ReadString('\n') |
|
|
line = strings.TrimSpace(line) |
|
|
|
|
|
if readErr != nil || line == "" { |
|
|
break |
|
|
} |
|
|
|
|
|
|
|
|
p := strings.SplitN(line, ":", headerParts) |
|
|
key := p[0] |
|
|
value := "" |
|
|
if len(p) == headerParts { |
|
|
value = p[1] |
|
|
} |
|
|
|
|
|
if !unsafe { |
|
|
if len(p) != headerParts { |
|
|
continue |
|
|
} |
|
|
|
|
|
if strings.EqualFold(key, "content-length") { |
|
|
continue |
|
|
} |
|
|
|
|
|
key = strings.TrimSpace(key) |
|
|
value = strings.TrimSpace(value) |
|
|
} |
|
|
|
|
|
headers[key] = value |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if strings.HasPrefix(parts[1], "http") { |
|
|
var parsed *url.URL |
|
|
parsed, err = url.Parse(parts[1]) |
|
|
if err != nil { |
|
|
err = fmt.Errorf("could not parse request URL: %s", err) |
|
|
return |
|
|
} |
|
|
path = parts[1] |
|
|
headers["Host"] = parsed.Host |
|
|
} else { |
|
|
path = parts[1] |
|
|
} |
|
|
|
|
|
|
|
|
b, err := ioutil.ReadAll(reader) |
|
|
if err != nil { |
|
|
err = fmt.Errorf("could not read request body: %s", err) |
|
|
return |
|
|
} |
|
|
body = string(b) |
|
|
|
|
|
return method, path, headers, body, nil |
|
|
} |
|
|
|