bettercap/modules/http_proxy/http_proxy_js_request.go
2024-08-09 18:19:21 +02:00

244 lines
5.5 KiB
Go

package http_proxy
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strings"
"github.com/bettercap/bettercap/v2/session"
)
type JSRequest struct {
Client map[string]string
Method string
Version string
Scheme string
Path string
Query string
Hostname string
Port string
ContentType string
Headers string
Body string
req *http.Request
refHash string
bodyRead bool
}
var header_regexp = regexp.MustCompile(`^\s*(.*?)\s*:\s*(.*)\s*$`)
func NewJSRequest(req *http.Request) *JSRequest {
headers := ""
cType := ""
for name, values := range req.Header {
for _, value := range values {
headers += name + ": " + value + "\r\n"
if strings.ToLower(name) == "content-type" {
cType = value
}
}
}
client_ip := strings.Split(req.RemoteAddr, ":")[0]
client_mac := ""
client_alias := ""
if endpoint := session.I.Lan.GetByIp(client_ip); endpoint != nil {
client_mac = endpoint.HwAddress
client_alias = endpoint.Alias
}
jreq := &JSRequest{
Client: map[string]string{"IP": client_ip, "MAC": client_mac, "Alias": client_alias},
Method: req.Method,
Version: fmt.Sprintf("%d.%d", req.ProtoMajor, req.ProtoMinor),
Scheme: req.URL.Scheme,
Hostname: req.URL.Hostname(),
Port: req.URL.Port(),
Path: req.URL.Path,
Query: req.URL.RawQuery,
ContentType: cType,
Headers: headers,
req: req,
bodyRead: false,
}
jreq.UpdateHash()
return jreq
}
func (j *JSRequest) NewHash() string {
hash := fmt.Sprintf("%s.%s.%s.%s.%s.%s.%s.%s.%s.%s",
j.Client["IP"],
j.Method,
j.Version,
j.Scheme,
j.Hostname,
j.Port,
j.Path,
j.Query,
j.ContentType,
j.Headers)
hash += "." + j.Body
return hash
}
func (j *JSRequest) UpdateHash() {
j.refHash = j.NewHash()
}
func (j *JSRequest) WasModified() bool {
// body was read
if j.bodyRead {
return true
}
// check if any of the fields has been changed
return j.NewHash() != j.refHash
}
func (j *JSRequest) GetHeader(name, deflt string) string {
headers := strings.Split(j.Headers, "\r\n")
for i := 0; i < len(headers); i++ {
if headers[i] != "" {
header_parts := header_regexp.FindAllSubmatch([]byte(headers[i]), 1)
if len(header_parts) != 0 && len(header_parts[0]) == 3 {
header_name := string(header_parts[0][1])
header_value := string(header_parts[0][2])
if strings.ToLower(name) == strings.ToLower(header_name) {
return header_value
}
}
}
}
return deflt
}
func (j *JSRequest) SetHeader(name, value string) {
name = strings.TrimSpace(name)
value = strings.TrimSpace(value)
if strings.ToLower(name) == "content-type" {
j.ContentType = value
}
headers := strings.Split(j.Headers, "\r\n")
for i := 0; i < len(headers); i++ {
if headers[i] != "" {
header_parts := header_regexp.FindAllSubmatch([]byte(headers[i]), 1)
if len(header_parts) != 0 && len(header_parts[0]) == 3 {
header_name := string(header_parts[0][1])
header_value := string(header_parts[0][2])
if strings.ToLower(name) == strings.ToLower(header_name) {
old_header := header_name + ": " + header_value + "\r\n"
new_header := name + ": " + value + "\r\n"
j.Headers = strings.Replace(j.Headers, old_header, new_header, 1)
return
}
}
}
}
j.Headers += name + ": " + value + "\r\n"
}
func (j *JSRequest) RemoveHeader(name string) {
headers := strings.Split(j.Headers, "\r\n")
for i := 0; i < len(headers); i++ {
if headers[i] != "" {
header_parts := header_regexp.FindAllSubmatch([]byte(headers[i]), 1)
if len(header_parts) != 0 && len(header_parts[0]) == 3 {
header_name := string(header_parts[0][1])
header_value := string(header_parts[0][2])
if strings.ToLower(name) == strings.ToLower(header_name) {
removed_header := header_name + ": " + header_value + "\r\n"
j.Headers = strings.Replace(j.Headers, removed_header, "", 1)
return
}
}
}
}
}
func (j *JSRequest) ReadBody() string {
raw, err := ioutil.ReadAll(j.req.Body)
if err != nil {
return ""
}
j.Body = string(raw)
j.bodyRead = true
// reset the request body to the original unread state
j.req.Body = ioutil.NopCloser(bytes.NewBuffer(raw))
return j.Body
}
func (j *JSRequest) ParseForm() map[string]string {
if j.Body == "" {
j.Body = j.ReadBody()
}
form := make(map[string]string)
parts := strings.Split(j.Body, "&")
for _, part := range parts {
nv := strings.SplitN(part, "=", 2)
if len(nv) == 2 {
unescaped, err := url.QueryUnescape(nv[1])
if err == nil {
form[nv[0]] = unescaped
} else {
form[nv[0]] = nv[1]
}
}
}
return form
}
func (j *JSRequest) ToRequest() (req *http.Request) {
portPart := ""
if j.Port != "" {
portPart = fmt.Sprintf(":%s", j.Port)
}
url := fmt.Sprintf("%s://%s%s%s?%s", j.Scheme, j.Hostname, portPart, j.Path, j.Query)
if j.Body == "" {
req, _ = http.NewRequest(j.Method, url, j.req.Body)
} else {
req, _ = http.NewRequest(j.Method, url, strings.NewReader(j.Body))
}
headers := strings.Split(j.Headers, "\r\n")
for i := 0; i < len(headers); i++ {
if headers[i] != "" {
header_parts := header_regexp.FindAllSubmatch([]byte(headers[i]), 1)
if len(header_parts) != 0 && len(header_parts[0]) == 3 {
header_name := string(header_parts[0][1])
header_value := string(header_parts[0][2])
if strings.ToLower(header_name) == "content-type" {
if header_value != j.ContentType {
req.Header.Set(header_name, j.ContentType)
continue
}
}
req.Header.Set(header_name, header_value)
}
}
}
req.RemoteAddr = j.Client["IP"]
return
}