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.

1040 lines
31 KiB

// Puppy provices an interface to create a proxy to intercept and modify HTTP and websocket messages passing through the proxy
package puppy
import (
"crypto/tls"
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket"
)
var getNextSubId = IdCounter()
var getNextStorageId = IdCounter()
// ProxyWebUIHandler is a function that can be used for handling web requests intended to be handled by the proxy
type ProxyWebUIHandler func(http.ResponseWriter, *http.Request, *InterceptingProxy)
type savedStorage struct {
storage MessageStorage
description string
}
type GlobalStorageWatcher interface {
// Callback for when a new request is saved
NewRequestSaved(storageId int, ms MessageStorage, req *ProxyRequest)
// Callback for when a request is updated
RequestUpdated(storageId int, ms MessageStorage, req *ProxyRequest)
// Callback for when a request is deleted
RequestDeleted(storageId int, ms MessageStorage, DbId string)
// Callback for when a new response is saved
NewResponseSaved(storageId int, ms MessageStorage, rsp *ProxyResponse)
// Callback for when a response is updated
ResponseUpdated(storageId int, ms MessageStorage, rsp *ProxyResponse)
// Callback for when a response is deleted
ResponseDeleted(storageId int, ms MessageStorage, DbId string)
// Callback for when a new wsmessage is saved
NewWSMessageSaved(storageId int, ms MessageStorage, req *ProxyRequest, wsm *ProxyWSMessage)
// Callback for when a wsmessage is updated
WSMessageUpdated(storageId int, ms MessageStorage, req *ProxyRequest, wsm *ProxyWSMessage)
// Callback for when a wsmessage is deleted
WSMessageDeleted(storageId int, ms MessageStorage, DbId string)
}
type globalWatcher struct {
watchers []GlobalStorageWatcher
}
type globalWatcherShim struct {
storageId int
globWatcher *globalWatcher
logger *log.Logger
}
// InterceptingProxy is a struct which represents a proxy which can intercept and modify HTTP and websocket messages
type InterceptingProxy struct {
slistener *ProxyListener
server *http.Server
mtx sync.Mutex
logger *log.Logger
proxyStorage int
netDial NetDialer
usingProxy bool
proxyHost string
proxyPort int
proxyIsSOCKS bool
proxyCreds *ProxyCredentials
requestInterceptor RequestInterceptor
responseInterceptor ResponseInterceptor
wSInterceptor WSInterceptor
scopeChecker RequestChecker
scopeQuery MessageQuery
reqSubs []*ReqIntSub
rspSubs []*RspIntSub
wsSubs []*WSIntSub
httpHandlers map[string]ProxyWebUIHandler
messageStorage map[int]*savedStorage
globWatcher *globalWatcher
}
// ProxyCredentials are a username/password combination used to represent an HTTP BasicAuth session
type ProxyCredentials struct {
Username string
Password string
}
// RequestInterceptor is a function that takes in a ProxyRequest and returns a modified ProxyRequest or nil to represent dropping the request
type RequestInterceptor func(req *ProxyRequest) (*ProxyRequest, error)
// ResponseInterceptor is a function that takes in a ProxyResponse and the original request and returns a modified ProxyResponse or nil to represent dropping the response
type ResponseInterceptor func(req *ProxyRequest, rsp *ProxyResponse) (*ProxyResponse, error)
// WSInterceptor is a function that takes in a ProxyWSMessage and the ProxyRequest/ProxyResponse which made up its handshake and returns and returns a modified ProxyWSMessage or nil to represent dropping the message. A WSInterceptor should be able to modify messages originating from both the client and the remote server.
type WSInterceptor func(req *ProxyRequest, rsp *ProxyResponse, msg *ProxyWSMessage) (*ProxyWSMessage, error)
// ReqIntSub represents an active HTTP request interception session in an InterceptingProxy
type ReqIntSub struct {
id int
Interceptor RequestInterceptor
}
// RspIntSub represents an active HTTP response interception session in an InterceptingProxy
type RspIntSub struct {
id int
Interceptor ResponseInterceptor
}
// WSIntSub represents an active websocket interception session in an InterceptingProxy
type WSIntSub struct {
id int
Interceptor WSInterceptor
}
// SerializeHeader serializes the ProxyCredentials into a value that can be included in an Authorization header
func (creds *ProxyCredentials) SerializeHeader() string {
toEncode := []byte(fmt.Sprintf("%s:%s", creds.Username, creds.Password))
encoded := base64.StdEncoding.EncodeToString(toEncode)
return fmt.Sprintf("Basic %s", encoded)
}
// NewInterceptingProxy will create a new InterceptingProxy and have it log using the provided logger. If logger is nil, the proxy will log to ioutil.Discard
func NewInterceptingProxy(logger *log.Logger) *InterceptingProxy {
var iproxy InterceptingProxy
var useLogger *log.Logger
if logger != nil {
useLogger = logger
} else {
useLogger = log.New(ioutil.Discard, "[*] ", log.Lshortfile)
}
iproxy.messageStorage = make(map[int]*savedStorage)
iproxy.slistener = NewProxyListener(useLogger)
iproxy.server = newProxyServer(useLogger, &iproxy)
iproxy.logger = useLogger
iproxy.httpHandlers = make(map[string]ProxyWebUIHandler)
iproxy.globWatcher = &globalWatcher{
watchers: make([]GlobalStorageWatcher, 0),
}
go func() {
iproxy.server.Serve(iproxy.slistener)
}()
return &iproxy
}
// Close closes all listeners being used by the proxy. Does not shut down internal HTTP server because there is no way to gracefully shut down an http server yet.
func (iproxy *InterceptingProxy) Close() {
// Will throw errors when the server finally shuts down and tries to call iproxy.slistener.Close a second time
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.slistener.Close()
//iproxy.server.Close() // Coming eventually... I hope
}
// LoadCACertificates loads a private/public key pair which should be used when generating self-signed certs for TLS connections
func (iproxy *InterceptingProxy) LoadCACertificates(certFile, keyFile string) error {
caCert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return fmt.Errorf("could not load certificate pair: %s", err.Error())
}
iproxy.SetCACertificate(&caCert)
return nil
}
// SetCACertificate sets certificate which should be used when generating self-signed certs for TLS connections
func (iproxy *InterceptingProxy) SetCACertificate(caCert *tls.Certificate) {
if iproxy.slistener == nil {
panic("intercepting proxy does not have a proxy listener")
}
iproxy.slistener.SetCACertificate(caCert)
}
// GetCACertificate returns certificate used to self-sign certificates for TLS connections
func (iproxy *InterceptingProxy) GetCACertificate() *tls.Certificate {
return iproxy.slistener.GetCACertificate()
}
// AddListener will have the proxy listen for HTTP connections on a listener. Proxy will attempt to strip TLS from the connection
func (iproxy *InterceptingProxy) AddListener(l net.Listener) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.slistener.AddListener(l)
}
// Have the proxy listen for HTTP connections on a listener and transparently redirect them to the destination. Listeners added this way can only redirect requests to a single destination. However, it does not rely on the client being aware that it is using an HTTP proxy.
func (iproxy *InterceptingProxy) AddTransparentListener(l net.Listener, destHost string, destPort int, useTLS bool) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.slistener.AddTransparentListener(l, destHost, destPort, useTLS)
}
// RemoveListner will have the proxy stop listening to a listener
func (iproxy *InterceptingProxy) RemoveListener(l net.Listener) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.slistener.RemoveListener(l)
}
// GetMessageStorage takes in a storage ID and returns the storage associated with the ID
func (iproxy *InterceptingProxy) GetMessageStorage(id int) (MessageStorage, string) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
savedStorage, ok := iproxy.messageStorage[id]
if !ok {
return nil, ""
}
return savedStorage.storage, savedStorage.description
}
// AddMessageStorage associates a MessageStorage with the proxy and returns an ID to be used when referencing the storage in the future
func (iproxy *InterceptingProxy) AddMessageStorage(storage MessageStorage, description string) int {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
id := getNextStorageId()
iproxy.messageStorage[id] = &savedStorage{storage, description}
shim := &globalWatcherShim{
storageId: id,
globWatcher: iproxy.globWatcher,
logger: iproxy.logger,
}
storage.Watch(shim)
return id
}
// CloseMessageStorage closes a message storage associated with the proxy
func (iproxy *InterceptingProxy) CloseMessageStorage(id int) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
savedStorage, ok := iproxy.messageStorage[id]
if !ok {
return
}
delete(iproxy.messageStorage, id)
savedStorage.storage.Close()
}
// SavedStorage represents a storage associated with the proxy
type SavedStorage struct {
Id int
Storage MessageStorage
Description string
}
// ListMessageStorage returns a list of storages associated with the proxy
func (iproxy *InterceptingProxy) ListMessageStorage() []*SavedStorage {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
r := make([]*SavedStorage, 0)
for id, ss := range iproxy.messageStorage {
r = append(r, &SavedStorage{id, ss.storage, ss.description})
}
return r
}
func (iproxy *InterceptingProxy) getRequestSubs() []*ReqIntSub {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
return iproxy.reqSubs
}
func (iproxy *InterceptingProxy) getResponseSubs() []*RspIntSub {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
return iproxy.rspSubs
}
func (iproxy *InterceptingProxy) getWSSubs() []*WSIntSub {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
return iproxy.wsSubs
}
// LoadScope loads the scope from the given storage and applies it to the proxy
func (iproxy *InterceptingProxy) LoadScope(storageId int) error {
// Try and set the scope
savedStorage, ok := iproxy.messageStorage[storageId]
if !ok {
return fmt.Errorf("proxy has no associated storage")
}
iproxy.logger.Println("loading scope")
if scope, err := savedStorage.storage.LoadQuery("__scope"); err == nil {
if err := iproxy.setScopeQuery(scope); err != nil {
iproxy.logger.Println("error setting scope:", err.Error())
}
} else {
iproxy.logger.Println("error loading scope:", err.Error())
}
return nil
}
// GetScopeChecker creates a RequestChecker which checks if a request matches the proxy's current scope
func (iproxy *InterceptingProxy) GetScopeChecker() RequestChecker {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
return iproxy.scopeChecker
}
// SetScopeChecker has the proxy use a specific RequestChecker to check if a request is in scope. If the checker returns true for a request it is considered in scope. Otherwise it is considered out of scope.
func (iproxy *InterceptingProxy) SetScopeChecker(checker RequestChecker) error {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
savedStorage, ok := iproxy.messageStorage[iproxy.proxyStorage]
if !ok {
savedStorage = nil
}
iproxy.scopeChecker = checker
iproxy.scopeQuery = nil
emptyQuery := make(MessageQuery, 0)
if savedStorage != nil {
savedStorage.storage.SaveQuery("__scope", emptyQuery) // Assume it clears it I guess
}
return nil
}
// GetScopeQuery returns the query associated with the proxy's scope. If the scope was set using SetScopeChecker, nil is returned
func (iproxy *InterceptingProxy) GetScopeQuery() MessageQuery {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
return iproxy.scopeQuery
}
// SetScopeQuery sets the scope of the proxy to include any request which matches the given MessageQuery
func (iproxy *InterceptingProxy) SetScopeQuery(query MessageQuery) error {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
return iproxy.setScopeQuery(query)
}
func (iproxy *InterceptingProxy) setScopeQuery(query MessageQuery) error {
checker, err := CheckerFromMessageQuery(query)
if err != nil {
return err
}
savedStorage, ok := iproxy.messageStorage[iproxy.proxyStorage]
if !ok {
savedStorage = nil
}
iproxy.scopeChecker = checker
iproxy.scopeQuery = query
if savedStorage != nil {
if err = savedStorage.storage.SaveQuery("__scope", query); err != nil {
return fmt.Errorf("could not save scope to storage: %s", err.Error())
}
}
return nil
}
// ClearScope removes all scope checks from the proxy so that all requests passing through the proxy will be considered in-scope
func (iproxy *InterceptingProxy) ClearScope() error {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.scopeChecker = nil
iproxy.scopeChecker = nil
emptyQuery := make(MessageQuery, 0)
savedStorage, ok := iproxy.messageStorage[iproxy.proxyStorage]
if !ok {
savedStorage = nil
}
if savedStorage != nil {
if err := savedStorage.storage.SaveQuery("__scope", emptyQuery); err != nil {
return fmt.Errorf("could not clear scope in storage: %s", err.Error())
}
}
return nil
}
// SetNetDial sets the NetDialer that should be used to create outgoing connections when submitting HTTP requests. Overwrites the request's NetDialer
func (iproxy *InterceptingProxy) SetNetDial(dialer NetDialer) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.netDial = dialer
}
// NetDial returns the dialer currently being used to create outgoing connections when submitting HTTP requests
func (iproxy *InterceptingProxy) NetDial() NetDialer {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
return iproxy.netDial
}
// ClearUpstreamProxy stops the proxy from using an upstream proxy for future connections
func (iproxy *InterceptingProxy) ClearUpstreamProxy() {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.usingProxy = false
iproxy.proxyHost = ""
iproxy.proxyPort = 0
iproxy.proxyIsSOCKS = false
}
// SetUpstreamProxy causes the proxy to begin using an upstream HTTP proxy for submitted HTTP requests
func (iproxy *InterceptingProxy) SetUpstreamProxy(proxyHost string, proxyPort int, creds *ProxyCredentials) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.usingProxy = true
iproxy.proxyHost = proxyHost
iproxy.proxyPort = proxyPort
iproxy.proxyIsSOCKS = false
iproxy.proxyCreds = creds
}
// SetUpstreamSOCKSProxy causes the proxy to begin using an upstream SOCKS proxy for submitted HTTP requests
func (iproxy *InterceptingProxy) SetUpstreamSOCKSProxy(proxyHost string, proxyPort int, creds *ProxyCredentials) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.usingProxy = true
iproxy.proxyHost = proxyHost
iproxy.proxyPort = proxyPort
iproxy.proxyIsSOCKS = true
iproxy.proxyCreds = creds
}
// SubmitRequest submits a ProxyRequest. Does not automatically save the request/results to proxy storage
func (iproxy *InterceptingProxy) SubmitRequest(req *ProxyRequest) error {
oldDial := req.NetDial
defer func() { req.NetDial = oldDial }()
req.NetDial = iproxy.NetDial()
if iproxy.usingProxy {
if iproxy.proxyIsSOCKS {
return SubmitRequestSOCKSProxy(req, iproxy.proxyHost, iproxy.proxyPort, iproxy.proxyCreds)
} else {
return SubmitRequestProxy(req, iproxy.proxyHost, iproxy.proxyPort, iproxy.proxyCreds)
}
}
return SubmitRequest(req)
}
// WSDial dials a remote server and submits the given request to initiate the handshake
func (iproxy *InterceptingProxy) WSDial(req *ProxyRequest) (*WSSession, error) {
oldDial := req.NetDial
defer func() { req.NetDial = oldDial }()
req.NetDial = iproxy.NetDial()
if iproxy.usingProxy {
if iproxy.proxyIsSOCKS {
return WSDialSOCKSProxy(req, iproxy.proxyHost, iproxy.proxyPort, iproxy.proxyCreds)
} else {
return WSDialProxy(req, iproxy.proxyHost, iproxy.proxyPort, iproxy.proxyCreds)
}
}
return WSDial(req)
}
// AddReqInterceptor adds a RequestInterceptor to the proxy which will be used to modify HTTP requests as they pass through the proxy. Returns a struct representing the active interceptor.
func (iproxy *InterceptingProxy) AddReqInterceptor(f RequestInterceptor) *ReqIntSub {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
sub := &ReqIntSub{
id: getNextSubId(),
Interceptor: f,
}
iproxy.reqSubs = append(iproxy.reqSubs, sub)
return sub
}
// RemoveReqInterceptor removes an active request interceptor from the proxy
func (iproxy *InterceptingProxy) RemoveReqInterceptor(sub *ReqIntSub) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
for i, checkSub := range iproxy.reqSubs {
if checkSub.id == sub.id {
iproxy.reqSubs = append(iproxy.reqSubs[:i], iproxy.reqSubs[i+1:]...)
return
}
}
}
// AddRspInterceptor adds a ResponseInterceptor to the proxy which will be used to modify HTTP responses as they pass through the proxy. Returns a struct representing the active interceptor.
func (iproxy *InterceptingProxy) AddRspInterceptor(f ResponseInterceptor) *RspIntSub {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
sub := &RspIntSub{
id: getNextSubId(),
Interceptor: f,
}
iproxy.rspSubs = append(iproxy.rspSubs, sub)
return sub
}
// RemoveRspInterceptor removes an active response interceptor from the proxy
func (iproxy *InterceptingProxy) RemoveRspInterceptor(sub *RspIntSub) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
for i, checkSub := range iproxy.rspSubs {
if checkSub.id == sub.id {
iproxy.rspSubs = append(iproxy.rspSubs[:i], iproxy.rspSubs[i+1:]...)
return
}
}
}
// AddWSInterceptor adds a WSInterceptor to the proxy which will be used to modify both incoming and outgoing websocket messages as they pass through the proxy. Returns a struct representing the active interceptor.
func (iproxy *InterceptingProxy) AddWSInterceptor(f WSInterceptor) *WSIntSub {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
sub := &WSIntSub{
id: getNextSubId(),
Interceptor: f,
}
iproxy.wsSubs = append(iproxy.wsSubs, sub)
return sub
}
// RemoveWSInterceptor removes an active websocket interceptor from the proxy
func (iproxy *InterceptingProxy) RemoveWSInterceptor(sub *WSIntSub) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
for i, checkSub := range iproxy.wsSubs {
if checkSub.id == sub.id {
iproxy.wsSubs = append(iproxy.wsSubs[:i], iproxy.wsSubs[i+1:]...)
return
}
}
}
// Add a global storage watcher
func (iproxy *InterceptingProxy) GlobalStorageWatch(watcher GlobalStorageWatcher) error {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.globWatcher.watchers = append(iproxy.globWatcher.watchers, watcher)
return nil
}
// Remove a global storage watcher
func (iproxy *InterceptingProxy) GlobalStorageEndWatch(watcher GlobalStorageWatcher) error {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
var newWatched = make([]GlobalStorageWatcher, 0)
for _, testWatcher := range iproxy.globWatcher.watchers {
if (testWatcher != watcher) {
newWatched = append(newWatched, testWatcher)
}
}
iproxy.globWatcher.watchers = newWatched
return nil
}
// SetProxyStorage sets which storage should be used to store messages as they pass through the proxy
func (iproxy *InterceptingProxy) SetProxyStorage(storageId int) error {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.proxyStorage = storageId
_, ok := iproxy.messageStorage[iproxy.proxyStorage]
if !ok {
return fmt.Errorf("no storage with id %d", storageId)
}
iproxy.LoadScope(storageId)
return nil
}
// GetProxyStorage returns the storage being used to save messages as they pass through the proxy
func (iproxy *InterceptingProxy) GetProxyStorage() MessageStorage {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
savedStorage, ok := iproxy.messageStorage[iproxy.proxyStorage]
if !ok {
return nil
}
return savedStorage.storage
}
// AddHTTPHandler causes the proxy to redirect requests to a host to an HTTPHandler. This can be used, for example, to create an internal web inteface. Be careful with what actions are allowed through the interface because the interface could be vulnerable to cross-site request forgery attacks.
func (iproxy *InterceptingProxy) AddHTTPHandler(host string, handler ProxyWebUIHandler) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.httpHandlers[host] = handler
}
// GetHTTPHandler returns the HTTP handler for a given host
func (iproxy *InterceptingProxy) GetHTTPHandler(host string) (ProxyWebUIHandler, error) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
handler, ok := iproxy.httpHandlers[host]
if !ok {
return nil, fmt.Errorf("no handler for host %s", host)
}
return handler, nil
}
// RemoveHTTPHandler removes the HTTP handler for a given host
func (iproxy *InterceptingProxy) RemoveHTTPHandler(host string) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
delete(iproxy.httpHandlers, host)
}
// ParseProxyRequest converts an http.Request read from a connection from a ProxyListener into a ProxyRequest
func ParseProxyRequest(r *http.Request) (*ProxyRequest, error) {
host, port, useTLS, err := DecodeRemoteAddr(r.RemoteAddr)
if err != nil {
return nil, nil
}
pr := NewProxyRequest(r, host, port, useTLS)
return pr, nil
}
// BlankResponse writes a blank response to a http.ResponseWriter. Used when a request/response is dropped.
func BlankResponse(w http.ResponseWriter) {
w.Header().Set("Connection", "close")
w.Header().Set("Cache-control", "no-cache")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Cache-control", "no-store")
w.Header().Set("X-Frame-Options", "DENY")
w.WriteHeader(200)
}
// ErrResponse writes an error response to the given http.ResponseWriter. Used to give proxy error information to the browser
func ErrResponse(w http.ResponseWriter, err error) {
w.Header().Set("Connection", "close")
w.Header().Set("Cache-control", "no-cache")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Cache-control", "no-store")
w.Header().Set("X-Frame-Options", "DENY")
http.Error(w, err.Error(), http.StatusInternalServerError)
}
// ServeHTTP is used to implement the interface required to have the proxy behave as an HTTP server
func (iproxy *InterceptingProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
handler, err := iproxy.GetHTTPHandler(r.Host)
if err == nil {
handler(w, r, iproxy)
return
}
req, _ := ParseProxyRequest(r)
iproxy.logger.Println("Received request to", req.FullURL().String())
req.StripProxyHeaders()
ms := iproxy.GetProxyStorage()
scopeChecker := iproxy.GetScopeChecker()
// Helper functions
checkScope := func(req *ProxyRequest) bool {
if scopeChecker != nil {
return scopeChecker(req)
}
return true
}
saveIfExists := func(req *ProxyRequest) error {
if ms != nil && checkScope(req) {
if err := UpdateRequest(ms, req); err != nil {
return err
}
}
return nil
}
/*
functions to mangle messages using the iproxy's manglers
each return the new message, whether it was modified, and an error
*/
mangleRequest := func(req *ProxyRequest) (*ProxyRequest, bool, error) {
newReq := req.Clone()
reqSubs := iproxy.getRequestSubs()
for _, sub := range reqSubs {
var err error = nil
newReq, err = sub.Interceptor(newReq)
if err != nil {
e := fmt.Errorf("error with request interceptor: %s", err)
return nil, false, e
}
if newReq == nil {
break
}
}
if newReq != nil {
newReq.StartDatetime = time.Now()
if !req.Eq(newReq) {
iproxy.logger.Println("Request modified by interceptor")
return newReq, true, nil
}
} else {
return nil, true, nil
}
return req, false, nil
}
mangleResponse := func(req *ProxyRequest, rsp *ProxyResponse) (*ProxyResponse, bool, error) {
reqCopy := req.Clone()
newRsp := rsp.Clone()
rspSubs := iproxy.getResponseSubs()
iproxy.logger.Printf("%d interceptors", len(rspSubs))
for _, sub := range rspSubs {
iproxy.logger.Println("mangling rsp...")
var err error = nil
newRsp, err = sub.Interceptor(reqCopy, newRsp)
if err != nil {
e := fmt.Errorf("error with response interceptor: %s", err)
return nil, false, e
}
if newRsp == nil {
break
}
}
if newRsp != nil {
if !rsp.Eq(newRsp) {
iproxy.logger.Println("Response for", req.FullURL(), "modified by interceptor")
// it was mangled
return newRsp, true, nil
}
} else {
// it was dropped
return nil, true, nil
}
// it wasn't changed
return rsp, false, nil
}
mangleWS := func(req *ProxyRequest, rsp *ProxyResponse, ws *ProxyWSMessage) (*ProxyWSMessage, bool, error) {
newMsg := ws.Clone()
reqCopy := req.Clone()
rspCopy := rsp.Clone()
wsSubs := iproxy.getWSSubs()
for _, sub := range wsSubs {
var err error = nil
newMsg, err = sub.Interceptor(reqCopy, rspCopy, newMsg)
if err != nil {
e := fmt.Errorf("error with ws interceptor: %s", err)
return nil, false, e
}
if newMsg == nil {
break
}
}
if newMsg != nil {
if !ws.Eq(newMsg) {
newMsg.Timestamp = time.Now()
newMsg.Direction = ws.Direction
iproxy.logger.Println("Message modified by interceptor")
return newMsg, true, nil
}
} else {
return nil, true, nil
}
return ws, false, nil
}
req.StartDatetime = time.Now()
if checkScope(req) {
if err := saveIfExists(req); err != nil {
ErrResponse(w, err)
return
}
newReq, mangled, err := mangleRequest(req)
if err != nil {
ErrResponse(w, err)
return
}
if mangled {
if newReq == nil {
req.ServerResponse = nil
if err := saveIfExists(req); err != nil {
ErrResponse(w, err)
return
}
BlankResponse(w)
return
}
newReq.Unmangled = req
req = newReq
req.StartDatetime = time.Now()
if err := saveIfExists(req); err != nil {
ErrResponse(w, err)
return
}
}
}
if req.IsWSUpgrade() {
iproxy.logger.Println("Detected websocket request. Upgrading...")
rc, err := iproxy.WSDial(req)
if err != nil {
iproxy.logger.Println("error dialing ws server:", err)
http.Error(w, fmt.Sprintf("error dialing websocket server: %s", err.Error()), http.StatusInternalServerError)
return
}
defer rc.Close()
req.EndDatetime = time.Now()
if err := saveIfExists(req); err != nil {
ErrResponse(w, err)
return
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
lc, err := upgrader.Upgrade(w, r, nil)
if err != nil {
iproxy.logger.Println("error upgrading connection:", err)
http.Error(w, fmt.Sprintf("error upgrading connection: %s", err.Error()), http.StatusInternalServerError)
return
}
defer lc.Close()
var wg sync.WaitGroup
var reqMtx sync.Mutex
addWSMessage := func(req *ProxyRequest, wsm *ProxyWSMessage) {
reqMtx.Lock()
defer reqMtx.Unlock()
req.WSMessages = append(req.WSMessages, wsm)
}
// Get messages from server
wg.Add(1)
go func() {
for {
mtype, msg, err := rc.ReadMessage()
if err != nil {
lc.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
iproxy.logger.Println("error with receiving server message:", err)
wg.Done()
return
}
pws, err := NewProxyWSMessage(mtype, msg, ToClient)
if err != nil {
iproxy.logger.Println("error creating ws object:", err.Error())
continue
}
pws.Timestamp = time.Now()
if checkScope(req) {
newMsg, mangled, err := mangleWS(req, req.ServerResponse, pws)
if err != nil {
iproxy.logger.Println("error mangling ws:", err)
return
}
if mangled {
if newMsg == nil {
continue
} else {
newMsg.Unmangled = pws
pws = newMsg
pws.Request = nil
}
}
}
addWSMessage(req, pws)
if err := saveIfExists(req); err != nil {
iproxy.logger.Println("error saving request:", err)
continue
}
lc.WriteMessage(pws.Type, pws.Message)
}
}()
// Get messages from client
wg.Add(1)
go func() {
for {
mtype, msg, err := lc.ReadMessage()
if err != nil {
rc.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
iproxy.logger.Println("error with receiving client message:", err)
wg.Done()
return
}
pws, err := NewProxyWSMessage(mtype, msg, ToServer)
if err != nil {
iproxy.logger.Println("error creating ws object:", err.Error())
continue
}
pws.Timestamp = time.Now()
if checkScope(req) {
newMsg, mangled, err := mangleWS(req, req.ServerResponse, pws)
if err != nil {
iproxy.logger.Println("error mangling ws:", err)
return
}
if mangled {
if newMsg == nil {
continue
} else {
newMsg.Unmangled = pws
pws = newMsg
pws.Request = nil
}
}
}
addWSMessage(req, pws)
if err := saveIfExists(req); err != nil {
iproxy.logger.Println("error saving request:", err)
continue
}
rc.WriteMessage(pws.Type, pws.Message)
}
}()
wg.Wait()
iproxy.logger.Println("Websocket session complete!")
} else {
err := iproxy.SubmitRequest(req)
if err != nil {
http.Error(w, fmt.Sprintf("error submitting request: %s", err.Error()), http.StatusInternalServerError)
return
}
req.EndDatetime = time.Now()
if err := saveIfExists(req); err != nil {
ErrResponse(w, err)
return
}
if checkScope(req) {
newRsp, mangled, err := mangleResponse(req, req.ServerResponse)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if mangled {
if newRsp == nil {
req.ServerResponse = nil
if err := saveIfExists(req); err != nil {
ErrResponse(w, err)
return
}
BlankResponse(w)
return
}
newRsp.Unmangled = req.ServerResponse
req.ServerResponse = newRsp
if err := saveIfExists(req); err != nil {
ErrResponse(w, err)
return
}
}
}
for k, v := range req.ServerResponse.Header {
for _, vv := range v {
w.Header().Add(k, vv)
}
}
w.WriteHeader(req.ServerResponse.StatusCode)
w.Write(req.ServerResponse.BodyBytes())
return
}
}
func newProxyServer(logger *log.Logger, iproxy *InterceptingProxy) *http.Server {
server := &http.Server{
Handler: iproxy,
ErrorLog: logger,
}
return server
}
// StorageWatcher implementation
func (watcher *globalWatcherShim) NewRequestSaved(ms MessageStorage, req *ProxyRequest) {
for _, w := range watcher.globWatcher.watchers {
w.NewRequestSaved(watcher.storageId, ms, req)
}
}
func (watcher *globalWatcherShim) RequestUpdated(ms MessageStorage, req *ProxyRequest) {
for _, w := range watcher.globWatcher.watchers {
w.RequestUpdated(watcher.storageId, ms, req)
}
}
func (watcher *globalWatcherShim) RequestDeleted(ms MessageStorage, DbId string) {
for _, w := range watcher.globWatcher.watchers {
w.RequestDeleted(watcher.storageId, ms, DbId)
}
}
func (watcher *globalWatcherShim) NewResponseSaved(ms MessageStorage, rsp *ProxyResponse) {
for _, w := range watcher.globWatcher.watchers {
w.NewResponseSaved(watcher.storageId, ms, rsp)
}
}
func (watcher *globalWatcherShim) ResponseUpdated(ms MessageStorage, rsp *ProxyResponse) {
for _, w := range watcher.globWatcher.watchers {
w.ResponseUpdated(watcher.storageId, ms, rsp)
}
}
func (watcher *globalWatcherShim) ResponseDeleted(ms MessageStorage, DbId string) {
for _, w := range watcher.globWatcher.watchers {
w.ResponseDeleted(watcher.storageId, ms, DbId)
}
}
func (watcher *globalWatcherShim) NewWSMessageSaved(ms MessageStorage, req *ProxyRequest, wsm *ProxyWSMessage) {
for _, w := range watcher.globWatcher.watchers {
w.NewWSMessageSaved(watcher.storageId, ms, req, wsm)
}
}
func (watcher *globalWatcherShim) WSMessageUpdated(ms MessageStorage, req *ProxyRequest, wsm *ProxyWSMessage) {
for _, w := range watcher.globWatcher.watchers {
w.WSMessageUpdated(watcher.storageId, ms, req, wsm)
}
}
func (watcher *globalWatcherShim) WSMessageDeleted(ms MessageStorage, DbId string) {
for _, w := range watcher.globWatcher.watchers {
w.WSMessageDeleted(watcher.storageId, ms, DbId)
}
}