/
plain.go
157 lines (138 loc) · 4.15 KB
/
plain.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package httpforward
import (
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
)
func usePlainForwardProxyHandler(ff *ForwardFroxy) *ForwardFroxy {
ff.handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// TODO: log management with on/off switch
// log.Println(req.RemoteAddr, "\t", req.Method, "\t", req.URL, "\t Host:", req.Host)
// log.Println("\t\t", req.Header)
if !ff.On {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("forward proxy is off"))
return
}
if !isAllowed(req, ff.Whitelist) {
w.Header().Set("Proxy-Authenticate", `Allowed realm="froxy dashboard"`)
w.WriteHeader(http.StatusProxyAuthRequired)
return
}
// for https tunneling
if req.Method == http.MethodConnect {
proxyConnect(w, req)
return
}
if !IsSchemeHTTPOrHTTPS(req.URL) {
http.Error(w, "unsupported scheme "+req.URL.Scheme, http.StatusBadRequest)
return
}
removeHeadersInConnectionHeader(req.Header)
removeHopHeaders(req.Header)
if ff.ForwardChainInfo {
appendSenderAddrToXForwaredForHeader(req.Header, req.RemoteAddr)
appendSenderAddrToForwardedHeader(req.Header, req)
}
client := &http.Client{}
resp, err := client.Do(reqWithClearedRequestURI(req))
if err != nil {
http.Error(w, "server-side request error", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
//log.Println(req.RemoteAddr, " ", resp.Status)
removeHeadersInConnectionHeader(resp.Header)
removeHopHeaders(resp.Header)
copyHeader(w.Header(), resp.Header)
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
})
return ff
}
func isAllowed(req *http.Request, whitelist map[string]struct{}) (ok bool) {
if _, ok = whitelist["*"]; ok {
return true
}
addr, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return false
}
_, ok = whitelist[addr]
return ok
}
func IsSchemeHTTPOrHTTPS(url *url.URL) bool {
return url.Scheme == "http" || url.Scheme == "https"
}
// removeHeadersInConnection removes headers in Connection field.
// The Connection header field allows the sender to specify options that are desired "only for transport-level that connection".
func removeHeadersInConnectionHeader(hd http.Header) http.Header {
for _, f := range hd["Connection"] {
for _, sf := range strings.Split(f, ",") {
if sf = strings.TrimSpace(sf); sf != "" {
hd.Del(sf)
}
}
}
return hd
}
// Hop-by-hop headers. These are removed when sent to the backend.
// As of RFC 7230, hop-by-hop headers are required to appear in the
// Connection header field. These are the headers defined by the
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
// compatibility.
var hopHeaders = []string{
"Connection",
"Proxy-Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailer", // spelling per https://www.rfc-editor.org/errata_search.php?eid=4522
"Transfer-Encoding",
"Upgrade",
}
func removeHopHeaders(hd http.Header) {
for _, h := range hopHeaders {
hd.Del(h)
}
}
func appendSenderAddrToXForwaredForHeader(hd http.Header, remoteAddr string) {
sender, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
return
}
if prev, ok := hd["X-Forwarded-For"]; ok {
sender = strings.Join(prev, ", ") + ", " + sender
}
hd.Set("X-Forwarded-For", sender)
}
func appendSenderAddrToForwardedHeader(hd http.Header, req *http.Request) {
sender, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return
}
sender = fmt.Sprintf("by=%s;for=%s;host=%s;proto=%s", "hidden", sender, req.Host, req.URL.Scheme)
if prev, ok := hd["Forwarded"]; ok {
sender = strings.Join(prev, ", ") + sender
}
hd.Set("Forwarded", sender)
}
// ReqWithClearedRequestURI clears req.RequestURI.
// RequestURI field causes error if it is set in the HTTP Client request.
// It is set when the server receives an request, by parsing the request line(e.g. GET http://www.example.com/ HTTP/1.1)'s request target.
// Clear it to reuse the request.
func reqWithClearedRequestURI(req *http.Request) *http.Request {
req.RequestURI = ""
return req
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}