This is a fork of: https://github.com/roglew/puppy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

624 lines
14 KiB

package main
/*
Wrappers around http.Request and http.Response to add helper functions needed by the proxy
*/
import (
"bufio"
"bytes"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"reflect"
"strings"
"strconv"
"time"
"github.com/deckarep/golang-set"
"github.com/gorilla/websocket"
)
const (
ToServer = iota
ToClient
)
type ProxyResponse struct {
http.Response
bodyBytes []byte
DbId string // ID used by storage implementation. Blank string = unsaved
Unmangled *ProxyResponse
}
type ProxyRequest struct {
http.Request
// Destination connection info
DestHost string
DestPort int
DestUseTLS bool
// Associated messages
ServerResponse *ProxyResponse
WSMessages []*ProxyWSMessage
Unmangled *ProxyRequest
// Additional data
bodyBytes []byte
DbId string // ID used by storage implementation. Blank string = unsaved
StartDatetime time.Time
EndDatetime time.Time
tags mapset.Set
}
type WSSession struct {
websocket.Conn
Request *ProxyRequest // Request used for handshake
}
type ProxyWSMessage struct {
Type int
Message []byte
Direction int
Unmangled *ProxyWSMessage
Timestamp time.Time
Request *ProxyRequest
DbId string // ID used by storage implementation. Blank string = unsaved
}
func NewProxyRequest(r *http.Request, destHost string, destPort int, destUseTLS bool) (*ProxyRequest) {
var retReq *ProxyRequest
if r != nil {
// Write/reread the request to make sure we get all the extra headers Go adds into req.Header
buf := bytes.NewBuffer(make([]byte, 0))
r.Write(buf)
httpReq2, err := http.ReadRequest(bufio.NewReader(buf))
if err != nil {
panic(err)
}
retReq = &ProxyRequest{
*httpReq2,
destHost,
destPort,
destUseTLS,
nil,
make([]*ProxyWSMessage, 0),
nil,
make([]byte, 0),
"",
time.Unix(0, 0),
time.Unix(0, 0),
mapset.NewSet(),
}
} else {
newReq, _ := http.NewRequest("GET", "/", nil) // Ignore error since this should be run the same every time and shouldn't error
newReq.Header.Set("User-Agent", "Puppy-Proxy/1.0")
newReq.Host = destHost
retReq = &ProxyRequest{
*newReq,
destHost,
destPort,
destUseTLS,
nil,
make([]*ProxyWSMessage, 0),
nil,
make([]byte, 0),
"",
time.Unix(0, 0),
time.Unix(0, 0),
mapset.NewSet(),
}
}
// Load the body
bodyBuf, _ := ioutil.ReadAll(retReq.Body)
retReq.SetBodyBytes(bodyBuf)
return retReq
}
func ProxyRequestFromBytes(b []byte, destHost string, destPort int, destUseTLS bool) (*ProxyRequest, error) {
buf := bytes.NewBuffer(b)
httpReq, err := http.ReadRequest(bufio.NewReader(buf))
if err != nil {
return nil, err
}
return NewProxyRequest(httpReq, destHost, destPort, destUseTLS), nil
}
func NewProxyResponse(r *http.Response) (*ProxyResponse) {
// Write/reread the request to make sure we get all the extra headers Go adds into req.Header
oldClose := r.Close
r.Close = false
buf := bytes.NewBuffer(make([]byte, 0))
r.Write(buf)
r.Close = oldClose
httpRsp2, err := http.ReadResponse(bufio.NewReader(buf), nil)
if err != nil {
panic(err)
}
httpRsp2.Close = false
retRsp := &ProxyResponse{
*httpRsp2,
make([]byte, 0),
"",
nil,
}
bodyBuf, _ := ioutil.ReadAll(retRsp.Body)
retRsp.SetBodyBytes(bodyBuf)
return retRsp
}
func ProxyResponseFromBytes(b []byte) (*ProxyResponse, error) {
buf := bytes.NewBuffer(b)
httpRsp, err := http.ReadResponse(bufio.NewReader(buf), nil)
if err != nil {
return nil, err
}
return NewProxyResponse(httpRsp), nil
}
func NewProxyWSMessage(mtype int, message []byte, direction int) (*ProxyWSMessage, error) {
return &ProxyWSMessage{
Type: mtype,
Message: message,
Direction: direction,
Unmangled: nil,
Timestamp: time.Unix(0, 0),
DbId: "",
}, nil
}
func (req *ProxyRequest) DestScheme() string {
if req.IsWSUpgrade() {
if req.DestUseTLS {
return "wss"
} else {
return "ws"
}
} else {
if req.DestUseTLS {
return "https"
} else {
return "http"
}
}
}
func (req *ProxyRequest) FullURL() *url.URL {
// Same as req.URL but guarantees it will include the scheme, host, and port if necessary
var u url.URL
u = *(req.URL) // Copy the original req.URL
u.Host = req.Host
u.Scheme = req.DestScheme()
return &u
}
func (req *ProxyRequest) DestURL() *url.URL {
// Same as req.FullURL() but uses DestHost and DestPort for the host and port
var u url.URL
u = *(req.URL) // Copy the original req.URL
u.Scheme = req.DestScheme()
if req.DestUseTLS && req.DestPort == 443 ||
!req.DestUseTLS && req.DestPort == 80 {
u.Host = req.DestHost
} else {
u.Host = fmt.Sprintf("%s:%d", req.DestHost, req.DestPort)
}
return &u
}
func (req *ProxyRequest) Submit() error {
// Connect to the remote server
var conn net.Conn
var err error
dest := fmt.Sprintf("%s:%d", req.DestHost, req.DestPort)
if req.DestUseTLS {
// Use TLS
conn, err = tls.Dial("tcp", dest, nil)
if err != nil {
return err
}
} else {
// Use plaintext
conn, err = net.Dial("tcp", dest)
if err != nil {
return err
}
}
// Write the request to the connection
req.StartDatetime = time.Now()
req.RepeatableWrite(conn)
// Read a response from the server
httpRsp, err := http.ReadResponse(bufio.NewReader(conn), nil)
if err != nil {
return err
}
req.EndDatetime = time.Now()
prsp := NewProxyResponse(httpRsp)
req.ServerResponse = prsp
return nil
}
func (req *ProxyRequest) WSDial() (*WSSession, error) {
if !req.IsWSUpgrade() {
return nil, fmt.Errorf("could not start websocket session: request is not a websocket handshake request")
}
upgradeHeaders := make(http.Header)
for k, v := range req.Header {
for _, vv := range v {
if !(k == "Upgrade" ||
k == "Connection" ||
k == "Sec-Websocket-Key" ||
k == "Sec-Websocket-Version" ||
k == "Sec-Websocket-Extensions" ||
k == "Sec-Websocket-Protocol") {
upgradeHeaders.Add(k, vv)
}
}
}
dialer := &websocket.Dialer{}
conn, rsp, err := dialer.Dial(req.DestURL().String(), upgradeHeaders)
if err != nil {
return nil, fmt.Errorf("could not dial WebSocket server: %s", err)
}
req.ServerResponse = NewProxyResponse(rsp)
wsession := &WSSession{
*conn,
req,
}
return wsession, nil
}
func (req *ProxyRequest) IsWSUpgrade() bool {
for k, v := range req.Header {
for _, vv := range v {
if strings.ToLower(k) == "upgrade" && strings.Contains(vv, "websocket") {
return true
}
}
}
return false
}
func (req *ProxyRequest) StripProxyHeaders() {
if !req.IsWSUpgrade() {
req.Header.Del("Connection")
}
req.Header.Del("Accept-Encoding")
req.Header.Del("Proxy-Connection")
req.Header.Del("Proxy-Authenticate")
req.Header.Del("Proxy-Authorization")
}
func (req *ProxyRequest) Eq(other *ProxyRequest) bool {
if req.StatusLine() != other.StatusLine() ||
!reflect.DeepEqual(req.Header, other.Header) ||
bytes.Compare(req.BodyBytes(), other.BodyBytes()) != 0 ||
req.DestHost != other.DestHost ||
req.DestPort != other.DestPort ||
req.DestUseTLS != other.DestUseTLS {
return false
}
return true
}
func (req *ProxyRequest) Clone() (*ProxyRequest) {
buf := bytes.NewBuffer(make([]byte, 0))
req.RepeatableWrite(buf)
newReq, err := ProxyRequestFromBytes(buf.Bytes(), req.DestHost, req.DestPort, req.DestUseTLS)
if err != nil {
panic(err)
}
newReq.DestHost = req.DestHost
newReq.DestPort = req.DestPort
newReq.DestUseTLS = req.DestUseTLS
newReq.Header = CopyHeader(req.Header)
return newReq
}
func (req *ProxyRequest) DeepClone() (*ProxyRequest) {
// Returns a request with the same request, response, and associated websocket messages
newReq := req.Clone()
newReq.DbId = req.DbId
if req.Unmangled != nil {
newReq.Unmangled = req.Unmangled.DeepClone()
}
if req.ServerResponse != nil {
newReq.ServerResponse = req.ServerResponse.DeepClone()
}
for _, wsm := range req.WSMessages {
newReq.WSMessages = append(newReq.WSMessages, wsm.DeepClone())
}
return newReq
}
func (req *ProxyRequest) resetBodyReader() {
// yes I know this method isn't the most efficient, I'll fix it if it causes problems later
req.Body = ioutil.NopCloser(bytes.NewBuffer(req.BodyBytes()))
}
func (req *ProxyRequest) RepeatableWrite(w io.Writer) {
req.Write(w)
req.resetBodyReader()
}
func (req *ProxyRequest) BodyBytes() []byte {
return DuplicateBytes(req.bodyBytes)
}
func (req *ProxyRequest) SetBodyBytes(bs []byte) {
req.bodyBytes = bs
req.resetBodyReader()
// Parse the form if we can, ignore errors
req.ParseMultipartForm(1024*1024*1024) // 1GB for no good reason
req.ParseForm()
req.resetBodyReader()
req.Header.Set("Content-Length", strconv.Itoa(len(bs)))
}
func (req *ProxyRequest) FullMessage() []byte {
buf := bytes.NewBuffer(make([]byte, 0))
req.RepeatableWrite(buf)
return buf.Bytes()
}
func (req *ProxyRequest) PostParameters() (url.Values, error) {
vals, err := url.ParseQuery(string(req.BodyBytes()))
if err != nil {
return nil, err
}
return vals, nil
}
func (req *ProxyRequest) SetPostParameter(key string, value string) {
req.PostForm.Set(key, value)
req.SetBodyBytes([]byte(req.PostForm.Encode()))
}
func (req *ProxyRequest) AddPostParameter(key string, value string) {
req.PostForm.Add(key, value)
req.SetBodyBytes([]byte(req.PostForm.Encode()))
}
func (req *ProxyRequest) DeletePostParameter(key string, value string) {
req.PostForm.Del(key)
req.SetBodyBytes([]byte(req.PostForm.Encode()))
}
func (req *ProxyRequest) SetURLParameter(key string, value string) {
q := req.URL.Query()
q.Set(key, value)
req.URL.RawQuery = q.Encode()
req.ParseForm()
}
func (req *ProxyRequest) URLParameters() (url.Values) {
vals := req.URL.Query()
return vals
}
func (req *ProxyRequest) AddURLParameter(key string, value string) {
q := req.URL.Query()
q.Add(key, value)
req.URL.RawQuery = q.Encode()
req.ParseForm()
}
func (req *ProxyRequest) DeleteURLParameter(key string, value string) {
q := req.URL.Query()
q.Del(key)
req.URL.RawQuery = q.Encode()
req.ParseForm()
}
func (req *ProxyRequest) AddTag(tag string) {
req.tags.Add(tag)
}
func (req *ProxyRequest) CheckTag(tag string) bool {
return req.tags.Contains(tag)
}
func (req *ProxyRequest) RemoveTag(tag string) {
req.tags.Remove(tag)
}
func (req *ProxyRequest) ClearTags() {
req.tags.Clear()
}
func (req *ProxyRequest) Tags() []string {
items := req.tags.ToSlice()
retslice := make([]string, 0)
for _, item := range items {
str, ok := item.(string)
if ok {
retslice = append(retslice, str)
}
}
return retslice
}
func (req *ProxyRequest) HTTPPath() string {
// The path used in the http request
u := *req.URL
u.Scheme = ""
u.Host = ""
u.Opaque = ""
u.User = nil
return u.String()
}
func (req *ProxyRequest) StatusLine() string {
return fmt.Sprintf("%s %s %s", req.Method, req.HTTPPath(), req.Proto)
}
func (req *ProxyRequest) HeaderSection() (string) {
retStr := req.StatusLine()
retStr += "\r\n"
for k, vs := range req.Header {
for _, v := range vs {
retStr += fmt.Sprintf("%s: %s\r\n", k, v)
}
}
return retStr
}
func (rsp *ProxyResponse) resetBodyReader() {
// yes I know this method isn't the most efficient, I'll fix it if it causes problems later
rsp.Body = ioutil.NopCloser(bytes.NewBuffer(rsp.BodyBytes()))
}
func (rsp *ProxyResponse) RepeatableWrite(w io.Writer) {
rsp.Write(w)
rsp.resetBodyReader()
}
func (rsp *ProxyResponse) BodyBytes() []byte {
return DuplicateBytes(rsp.bodyBytes)
}
func (rsp *ProxyResponse) SetBodyBytes(bs []byte) {
rsp.bodyBytes = bs
rsp.resetBodyReader()
rsp.Header.Set("Content-Length", strconv.Itoa(len(bs)))
}
func (rsp *ProxyResponse) Clone() (*ProxyResponse) {
buf := bytes.NewBuffer(make([]byte, 0))
rsp.RepeatableWrite(buf)
newRsp, err := ProxyResponseFromBytes(buf.Bytes())
if err != nil {
panic(err)
}
return newRsp
}
func (rsp *ProxyResponse) DeepClone() (*ProxyResponse) {
newRsp := rsp.Clone()
newRsp.DbId = rsp.DbId
if rsp.Unmangled != nil {
newRsp.Unmangled = rsp.Unmangled.DeepClone()
}
return newRsp
}
func (rsp *ProxyResponse) Eq(other *ProxyResponse) bool {
if rsp.StatusLine() != other.StatusLine() ||
!reflect.DeepEqual(rsp.Header, other.Header) ||
bytes.Compare(rsp.BodyBytes(), other.BodyBytes()) != 0 {
return false
}
return true
}
func (rsp *ProxyResponse) FullMessage() []byte {
buf := bytes.NewBuffer(make([]byte, 0))
rsp.RepeatableWrite(buf)
return buf.Bytes()
}
func (rsp *ProxyResponse) HTTPStatus() string {
// The status text to be used in the http request
text := rsp.Status
if text == "" {
text = http.StatusText(rsp.StatusCode)
if text == "" {
text = "status code " + strconv.Itoa(rsp.StatusCode)
}
} else {
// Just to reduce stutter, if user set rsp.Status to "200 OK" and StatusCode to 200.
// Not important.
text = strings.TrimPrefix(text, strconv.Itoa(rsp.StatusCode)+" ")
}
return text
}
func (rsp *ProxyResponse) StatusLine() string {
// Status line, stolen from net/http/response.go
return fmt.Sprintf("HTTP/%d.%d %03d %s", rsp.ProtoMajor, rsp.ProtoMinor, rsp.StatusCode, rsp.HTTPStatus())
}
func (rsp *ProxyResponse) HeaderSection() (string) {
retStr := rsp.StatusLine()
retStr += "\r\n"
for k, vs := range rsp.Header {
for _, v := range vs {
retStr += fmt.Sprintf("%s: %s\r\n", k, v)
}
}
return retStr
}
func (msg *ProxyWSMessage) String() string {
var dirStr string
if msg.Direction == ToClient {
dirStr = "ToClient"
} else {
dirStr = "ToServer"
}
return fmt.Sprintf("{WS Message msg=\"%s\", type=%d, dir=%s}", string(msg.Message), msg.Type, dirStr)
}
func (msg *ProxyWSMessage) Clone() (*ProxyWSMessage) {
var retMsg ProxyWSMessage
retMsg.Type = msg.Type
retMsg.Message = msg.Message
retMsg.Direction = msg.Direction
retMsg.Timestamp = msg.Timestamp
retMsg.Request = msg.Request
return &retMsg
}
func (msg *ProxyWSMessage) DeepClone() (*ProxyWSMessage) {
retMsg := msg.Clone()
retMsg.DbId = msg.DbId
if msg.Unmangled != nil {
retMsg.Unmangled = msg.Unmangled.DeepClone()
}
return retMsg
}
func (msg *ProxyWSMessage) Eq(other *ProxyWSMessage) bool {
if msg.Type != other.Type ||
msg.Direction != other.Direction ||
bytes.Compare(msg.Message, other.Message) != 0 {
return false
}
return true
}
func CopyHeader(hd http.Header) (http.Header) {
var ret http.Header = make(http.Header)
for k, vs := range hd {
for _, v := range vs {
ret.Add(k, v)
}
}
return ret
}