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.

1041 lines
31 KiB

7 years ago
// Puppy provices an interface to create a proxy to intercept and modify HTTP and websocket messages passing through the proxy
package puppy
7 years ago
import (
"crypto/tls"
"encoding/base64"
7 years ago
"fmt"
7 years ago
"io/ioutil"
7 years ago
"log"
"net"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket"
)
var getNextSubId = IdCounter()
var getNextStorageId = IdCounter()
7 years ago
// 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)
7 years ago
type savedStorage struct {
storage MessageStorage
7 years ago
description string
}
6 years ago
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
}
7 years ago
// InterceptingProxy is a struct which represents a proxy which can intercept and modify HTTP and websocket messages
7 years ago
type InterceptingProxy struct {
slistener *ProxyListener
server *http.Server
mtx sync.Mutex
logger *log.Logger
7 years ago
proxyStorage int
netDial NetDialer
usingProxy bool
proxyHost string
proxyPort int
proxyIsSOCKS bool
proxyCreds *ProxyCredentials
7 years ago
requestInterceptor RequestInterceptor
7 years ago
responseInterceptor ResponseInterceptor
wSInterceptor WSInterceptor
scopeChecker RequestChecker
scopeQuery MessageQuery
7 years ago
reqSubs []*ReqIntSub
rspSubs []*RspIntSub
wsSubs []*WSIntSub
7 years ago
httpHandlers map[string]ProxyWebUIHandler
7 years ago
messageStorage map[int]*savedStorage
6 years ago
globWatcher *globalWatcher
7 years ago
}
7 years ago
// ProxyCredentials are a username/password combination used to represent an HTTP BasicAuth session
type ProxyCredentials struct {
Username string
Password string
}
7 years ago
// RequestInterceptor is a function that takes in a ProxyRequest and returns a modified ProxyRequest or nil to represent dropping the request
7 years ago
type RequestInterceptor func(req *ProxyRequest) (*ProxyRequest, error)
7 years ago
// 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
7 years ago
type ResponseInterceptor func(req *ProxyRequest, rsp *ProxyResponse) (*ProxyResponse, error)
7 years ago
// 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.
7 years ago
type WSInterceptor func(req *ProxyRequest, rsp *ProxyResponse, msg *ProxyWSMessage) (*ProxyWSMessage, error)
7 years ago
// ReqIntSub represents an active HTTP request interception session in an InterceptingProxy
7 years ago
type ReqIntSub struct {
id int
7 years ago
Interceptor RequestInterceptor
}
7 years ago
// RspIntSub represents an active HTTP response interception session in an InterceptingProxy
7 years ago
type RspIntSub struct {
id int
7 years ago
Interceptor ResponseInterceptor
}
7 years ago
// WSIntSub represents an active websocket interception session in an InterceptingProxy
7 years ago
type WSIntSub struct {
id int
7 years ago
Interceptor WSInterceptor
}
7 years ago
// 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)
}
7 years ago
// 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
7 years ago
func NewInterceptingProxy(logger *log.Logger) *InterceptingProxy {
var iproxy InterceptingProxy
7 years ago
var useLogger *log.Logger
if logger != nil {
useLogger = logger
} else {
useLogger = log.New(ioutil.Discard, "[*] ", log.Lshortfile)
}
7 years ago
iproxy.messageStorage = make(map[int]*savedStorage)
7 years ago
iproxy.slistener = NewProxyListener(useLogger)
iproxy.server = newProxyServer(useLogger, &iproxy)
iproxy.logger = useLogger
iproxy.httpHandlers = make(map[string]ProxyWebUIHandler)
6 years ago
iproxy.globWatcher = &globalWatcher{
watchers: make([]GlobalStorageWatcher, 0),
}
7 years ago
go func() {
iproxy.server.Serve(iproxy.slistener)
}()
return &iproxy
}
7 years ago
// 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.
7 years ago
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()
7 years ago
//iproxy.server.Close() // Coming eventually... I hope
}
7 years ago
// 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
7 years ago
func (iproxy *InterceptingProxy) SetCACertificate(caCert *tls.Certificate) {
if iproxy.slistener == nil {
panic("intercepting proxy does not have a proxy listener")
}
iproxy.slistener.SetCACertificate(caCert)
}
7 years ago
// GetCACertificate returns certificate used to self-sign certificates for TLS connections
func (iproxy *InterceptingProxy) GetCACertificate() *tls.Certificate {
7 years ago
return iproxy.slistener.GetCACertificate()
}
7 years ago
// AddListener will have the proxy listen for HTTP connections on a listener. Proxy will attempt to strip TLS from the connection
7 years ago
func (iproxy *InterceptingProxy) AddListener(l net.Listener) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.slistener.AddListener(l)
}
7 years ago
// 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
7 years ago
func (iproxy *InterceptingProxy) RemoveListener(l net.Listener) {
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
iproxy.slistener.RemoveListener(l)
}
7 years ago
// GetMessageStorage takes in a storage ID and returns the storage associated with the ID
7 years ago
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
}
7 years ago
// 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 {
7 years ago
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
id := getNextStorageId()
iproxy.messageStorage[id] = &savedStorage{storage, description}
6 years ago
shim := &globalWatcherShim{
storageId: id,
globWatcher: iproxy.globWatcher,
logger: iproxy.logger,
}
storage.Watch(shim)
7 years ago
return id
}
7 years ago
// CloseMessageStorage closes a message storage associated with the proxy
7 years ago
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()
}
7 years ago
// SavedStorage represents a storage associated with the proxy
7 years ago
type SavedStorage struct {
Id int
Storage MessageStorage
7 years ago
Description string
}
7 years ago
// ListMessageStorage returns a list of storages associated with the proxy
7 years ago
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
}
7 years ago
// LoadScope loads the scope from the given storage and applies it to the proxy
7 years ago
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
}
7 years ago
// GetScopeChecker creates a RequestChecker which checks if a request matches the proxy's current scope
func (iproxy *InterceptingProxy) GetScopeChecker() RequestChecker {
7 years ago
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
return iproxy.scopeChecker
}
7 years ago
// 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.
7 years ago
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
}
7 years ago
// 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 {
7 years ago
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
return iproxy.scopeQuery
}
7 years ago
// SetScopeQuery sets the scope of the proxy to include any request which matches the given MessageQuery
func (iproxy *InterceptingProxy) SetScopeQuery(query MessageQuery) error {
7 years ago
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
return iproxy.setScopeQuery(query)
}
func (iproxy *InterceptingProxy) setScopeQuery(query MessageQuery) error {
7 years ago
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
}
7 years ago
// 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
}
7 years ago
// 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
}
7 years ago
// 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
}
7 years ago
// 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
}
7 years ago
// 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
}
7 years ago
// 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)
}
7 years ago
// 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)
}
7 years ago
// 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 {
7 years ago
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
sub := &ReqIntSub{
id: getNextSubId(),
7 years ago
Interceptor: f,
}
iproxy.reqSubs = append(iproxy.reqSubs, sub)
return sub
}
7 years ago
// RemoveReqInterceptor removes an active request interceptor from the proxy
func (iproxy *InterceptingProxy) RemoveReqInterceptor(sub *ReqIntSub) {
7 years ago
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
}
}
}
7 years ago
// 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 {
7 years ago
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
sub := &RspIntSub{
id: getNextSubId(),
7 years ago
Interceptor: f,
}
iproxy.rspSubs = append(iproxy.rspSubs, sub)
return sub
}
7 years ago
// RemoveRspInterceptor removes an active response interceptor from the proxy
func (iproxy *InterceptingProxy) RemoveRspInterceptor(sub *RspIntSub) {
7 years ago
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
}
}
}
7 years ago
// 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 {
7 years ago
iproxy.mtx.Lock()
defer iproxy.mtx.Unlock()
sub := &WSIntSub{
id: getNextSubId(),
7 years ago
Interceptor: f,
}
iproxy.wsSubs = append(iproxy.wsSubs, sub)
return sub
}
7 years ago
// RemoveWSInterceptor removes an active websocket interceptor from the proxy
func (iproxy *InterceptingProxy) RemoveWSInterceptor(sub *WSIntSub) {
7 years ago
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
}
}
}
6 years ago
// 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
}
7 years ago
// SetProxyStorage sets which storage should be used to store messages as they pass through the proxy
7 years ago
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
}
7 years ago
// GetProxyStorage returns the storage being used to save messages as they pass through the proxy
7 years ago
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
}
7 years ago
// 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
}
7 years ago
// 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
}
7 years ago
// 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)
}
7 years ago
// ParseProxyRequest converts an http.Request read from a connection from a ProxyListener into a ProxyRequest
7 years ago
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
}
7 years ago
// BlankResponse writes a blank response to a http.ResponseWriter. Used when a request/response is dropped.
7 years ago
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)
}
7 years ago
// ErrResponse writes an error response to the given http.ResponseWriter. Used to give proxy error information to the browser
7 years ago
func ErrResponse(w http.ResponseWriter, err error) {
7 years ago
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")
7 years ago
http.Error(w, err.Error(), http.StatusInternalServerError)
}
7 years ago
// 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
}
7 years ago
req, _ := ParseProxyRequest(r)
iproxy.logger.Println("Received request to", req.FullURL().String())
7 years ago
req.StripProxyHeaders()
ms := iproxy.GetProxyStorage()
scopeChecker := iproxy.GetScopeChecker()
7 years ago
// 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
7 years ago
*/
mangleRequest := func(req *ProxyRequest) (*ProxyRequest, bool, error) {
newReq := req.Clone()
reqSubs := iproxy.getRequestSubs()
7 years ago
for _, sub := range reqSubs {
var err error = nil
newReq, err = sub.Interceptor(newReq)
7 years ago
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")
7 years ago
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))
7 years ago
for _, sub := range rspSubs {
iproxy.logger.Println("mangling rsp...")
7 years ago
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")
7 years ago
// 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()
7 years ago
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")
7 years ago
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...")
7 years ago
rc, err := iproxy.WSDial(req)
7 years ago
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)
7 years ago
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)
7 years ago
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)
7 years ago
wg.Done()
return
}
pws, err := NewProxyWSMessage(mtype, msg, ToClient)
if err != nil {
iproxy.logger.Println("error creating ws object:", err.Error())
7 years ago
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)
7 years ago
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)
7 years ago
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)
7 years ago
wg.Done()
return
}
pws, err := NewProxyWSMessage(mtype, msg, ToServer)
if err != nil {
iproxy.logger.Println("error creating ws object:", err.Error())
7 years ago
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)
7 years ago
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)
7 years ago
continue
}
rc.WriteMessage(pws.Type, pws.Message)
}
}()
wg.Wait()
iproxy.logger.Println("Websocket session complete!")
7 years ago
} else {
err := iproxy.SubmitRequest(req)
7 years ago
if err != nil {
http.Error(w, fmt.Sprintf("error submitting request: %s", err.Error()), http.StatusInternalServerError)
7 years ago
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
}
}
}
7 years ago
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,
7 years ago
ErrorLog: logger,
}
return server
}
6 years ago
// 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)
}
}