Skip to content

Commit 1fe7486

Browse files
jeanp413roboquat
authored andcommitted
Fix X-Forwarded-* headers
1 parent 6274cf1 commit 1fe7486

File tree

4 files changed

+82
-135
lines changed

4 files changed

+82
-135
lines changed

components/openvsx-proxy/pkg/handler.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ func (o *OpenVSXProxy) Handler(p *httputil.ReverseProxy) func(http.ResponseWrite
4949
key, err := o.key(r)
5050
if err != nil {
5151
log.WithFields(logFields).WithError(err).Error("cannot create cache key")
52-
r.Host = o.upstreamURL.Host
5352
p.ServeHTTP(rw, r)
5453
o.finishLog(logFields, start, hitCacheRegular, hitCacheBackup)
5554
o.metrics.DurationRequestProcessingHistogram.Observe(time.Since(start).Seconds())
@@ -105,7 +104,6 @@ func (o *OpenVSXProxy) Handler(p *httputil.ReverseProxy) func(http.ResponseWrite
105104
log.WithFields(logFields).WithFields(o.DurationLogFields(duration)).Info("processing request finished")
106105
o.metrics.DurationRequestProcessingHistogram.Observe(duration.Seconds())
107106

108-
r.Host = o.upstreamURL.Host
109107
p.ServeHTTP(rw, r)
110108
o.finishLog(logFields, start, hitCacheRegular, hitCacheBackup)
111109
}

components/openvsx-proxy/pkg/modifyresponse.go

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ package pkg
66

77
import (
88
"bytes"
9-
"compress/gzip"
109
"fmt"
1110
"io/ioutil"
1211
"net/http"
1312
"strconv"
14-
"strings"
1513
"time"
1614
"unicode/utf8"
1715

@@ -55,7 +53,6 @@ func (o *OpenVSXProxy) ModifyResponse(r *http.Response) error {
5553
return err
5654
}
5755
r.Body.Close()
58-
r.Body = ioutil.NopCloser(bytes.NewBuffer(rawBody))
5956

6057
if r.StatusCode >= 500 || r.StatusCode == http.StatusTooManyRequests || r.StatusCode == http.StatusRequestTimeout {
6158
// use cache if exists
@@ -94,49 +91,9 @@ func (o *OpenVSXProxy) ModifyResponse(r *http.Response) error {
9491
}
9592

9693
// no error (status code < 500)
97-
body := rawBody
98-
contentType := r.Header.Get("Content-Type")
99-
if strings.HasPrefix(contentType, "application/json") {
100-
isCompressedResponse := strings.EqualFold(r.Header.Get("Content-Encoding"), "gzip")
101-
if isCompressedResponse {
102-
gzipReader, err := gzip.NewReader(ioutil.NopCloser(bytes.NewBuffer(rawBody)))
103-
if err != nil {
104-
log.WithFields(logFields).WithError(err)
105-
return nil
106-
}
107-
108-
body, err = ioutil.ReadAll(gzipReader)
109-
if err != nil {
110-
log.WithFields(logFields).WithError(err).Error("error reading compressed response body")
111-
return nil
112-
}
113-
gzipReader.Close()
114-
}
115-
116-
if log.Log.Level >= logrus.DebugLevel {
117-
log.WithFields(logFields).Debugf("replacing %d occurence(s) of '%s' in response body ...", strings.Count(string(body), o.Config.URLUpstream), o.Config.URLUpstream)
118-
}
119-
bodyStr := strings.ReplaceAll(string(body), o.Config.URLUpstream, o.Config.URLLocal)
120-
body = []byte(bodyStr)
121-
122-
if isCompressedResponse {
123-
var b bytes.Buffer
124-
gzipWriter := gzip.NewWriter(&b)
125-
_, err = gzipWriter.Write(body)
126-
if err != nil {
127-
log.WithFields(logFields).WithError(err).Error("error writing compressed response body")
128-
return nil
129-
}
130-
gzipWriter.Close()
131-
body = b.Bytes()
132-
}
133-
} else {
134-
log.WithFields(logFields).Debugf("response is not JSON but '%s', skipping replacing '%s' in response body", contentType, o.Config.URLUpstream)
135-
}
136-
13794
cacheObj := &CacheObject{
13895
Header: r.Header,
139-
Body: body,
96+
Body: rawBody,
14097
StatusCode: r.StatusCode,
14198
}
14299
err = o.StoreCache(key, cacheObj)
@@ -146,8 +103,8 @@ func (o *OpenVSXProxy) ModifyResponse(r *http.Response) error {
146103
log.WithFields(logFields).Info("successfully stored response to cache")
147104
}
148105

149-
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
150-
r.ContentLength = int64(len(body))
151-
r.Header.Set("Content-Length", strconv.Itoa(len(body)))
106+
r.Body = ioutil.NopCloser(bytes.NewBuffer(rawBody))
107+
r.ContentLength = int64(len(rawBody))
108+
r.Header.Set("Content-Length", strconv.Itoa(len(rawBody)))
152109
return nil
153110
}

components/openvsx-proxy/pkg/openvsxproxy_test.go

Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package pkg
66

77
import (
88
"bytes"
9-
"compress/gzip"
109
"fmt"
1110
"io"
1211
"net/http"
@@ -30,90 +29,6 @@ func createFrontend(backendURL string) (*httptest.Server, *OpenVSXProxy) {
3029
return frontend, openVSXProxy
3130
}
3231

33-
func TestReplaceHostInJSONResponse(t *testing.T) {
34-
backend := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
35-
bodyBytes, _ := io.ReadAll(r.Body)
36-
rw.Header().Set("Content-Type", "application/json")
37-
rw.Write([]byte(fmt.Sprintf("Hello %s!", string(bodyBytes))))
38-
}))
39-
defer backend.Close()
40-
41-
frontend, _ := createFrontend(backend.URL)
42-
defer frontend.Close()
43-
44-
frontendClient := frontend.Client()
45-
46-
requestBody := backend.URL
47-
req, _ := http.NewRequest("POST", frontend.URL, bytes.NewBuffer([]byte(requestBody)))
48-
req.Close = true
49-
res, err := frontendClient.Do(req)
50-
if err != nil {
51-
t.Fatal(err)
52-
}
53-
expectedResponse := fmt.Sprintf("Hello %s!", frontend.URL)
54-
if bodyBytes, _ := io.ReadAll(res.Body); string(bodyBytes) != expectedResponse {
55-
t.Errorf("got body '%s'; expected '%s'", string(bodyBytes), expectedResponse)
56-
}
57-
}
58-
59-
func TestReplaceHostInCompressedJSONResponse(t *testing.T) {
60-
backend := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
61-
bodyBytes, _ := io.ReadAll(r.Body)
62-
rw.Header().Set("Content-Type", "application/json")
63-
rw.Header().Set("Content-Encoding", "gzip")
64-
65-
var b bytes.Buffer
66-
w := gzip.NewWriter(&b)
67-
w.Write([]byte(fmt.Sprintf("Hello %s!", string(bodyBytes))))
68-
w.Close()
69-
rw.Write(b.Bytes())
70-
}))
71-
defer backend.Close()
72-
73-
frontend, _ := createFrontend(backend.URL)
74-
defer frontend.Close()
75-
76-
frontendClient := frontend.Client()
77-
78-
requestBody := backend.URL
79-
req, _ := http.NewRequest("POST", frontend.URL, bytes.NewBuffer([]byte(requestBody)))
80-
req.Close = true
81-
res, err := frontendClient.Do(req)
82-
if err != nil {
83-
t.Fatal(err)
84-
}
85-
expectedResponse := fmt.Sprintf("Hello %s!", frontend.URL)
86-
if bodyBytes, _ := io.ReadAll(res.Body); string(bodyBytes) != expectedResponse {
87-
t.Errorf("got body '%s'; expected '%s'", string(bodyBytes), expectedResponse)
88-
}
89-
}
90-
91-
func TestNotReplaceHostInNonJSONResponse(t *testing.T) {
92-
backend := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
93-
bodyBytes, _ := io.ReadAll(r.Body)
94-
rw.Header().Set("Content-Type", "application/octet-stream")
95-
rw.Write([]byte(fmt.Sprintf("Hello %s!", string(bodyBytes))))
96-
}))
97-
defer backend.Close()
98-
99-
frontend, _ := createFrontend(backend.URL)
100-
defer frontend.Close()
101-
102-
frontendClient := frontend.Client()
103-
104-
requestBody := backend.URL
105-
req, _ := http.NewRequest("POST", frontend.URL, bytes.NewBuffer([]byte(requestBody)))
106-
req.Close = true
107-
res, err := frontendClient.Do(req)
108-
if err != nil {
109-
t.Fatal(err)
110-
}
111-
expectedResponse := fmt.Sprintf("Hello %s!", backend.URL)
112-
if bodyBytes, _ := io.ReadAll(res.Body); string(bodyBytes) != expectedResponse {
113-
t.Errorf("got body '%s'; expected '%s'", string(bodyBytes), expectedResponse)
114-
}
115-
}
116-
11732
func TestAddResponseToCache(t *testing.T) {
11833
backend := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
11934
bodyBytes, _ := io.ReadAll(r.Body)

components/openvsx-proxy/pkg/run.go

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"net/http/httputil"
1111
"net/url"
12+
"strings"
1213
"time"
1314

1415
"github.com/eko/gocache/cache"
@@ -58,7 +59,7 @@ func (o *OpenVSXProxy) Start() (shutdown func(context.Context) error, err error)
5859
return nil, err
5960
}
6061
}
61-
proxy := httputil.NewSingleHostReverseProxy(o.upstreamURL)
62+
proxy := newSingleHostReverseProxy(o.upstreamURL)
6263
proxy.ErrorHandler = o.ErrorHandler
6364
proxy.ModifyResponse = o.ModifyResponse
6465
proxy.Transport = &DurationTrackingTransport{o: o}
@@ -112,3 +113,79 @@ func (t *DurationTrackingTransport) RoundTrip(r *http.Request) (*http.Response,
112113
}(start)
113114
return http.DefaultTransport.RoundTrip(r)
114115
}
116+
117+
// From go/src/net/http/httputil/reverseproxy.go
118+
119+
func singleJoiningSlash(a, b string) string {
120+
aslash := strings.HasSuffix(a, "/")
121+
bslash := strings.HasPrefix(b, "/")
122+
switch {
123+
case aslash && bslash:
124+
return a + b[1:]
125+
case !aslash && !bslash:
126+
return a + "/" + b
127+
}
128+
return a + b
129+
}
130+
131+
func joinURLPath(a, b *url.URL) (path, rawpath string) {
132+
if a.RawPath == "" && b.RawPath == "" {
133+
return singleJoiningSlash(a.Path, b.Path), ""
134+
}
135+
// Same as singleJoiningSlash, but uses EscapedPath to determine
136+
// whether a slash should be added
137+
apath := a.EscapedPath()
138+
bpath := b.EscapedPath()
139+
140+
aslash := strings.HasSuffix(apath, "/")
141+
bslash := strings.HasPrefix(bpath, "/")
142+
143+
switch {
144+
case aslash && bslash:
145+
return a.Path + b.Path[1:], apath + bpath[1:]
146+
case !aslash && !bslash:
147+
return a.Path + "/" + b.Path, apath + "/" + bpath
148+
}
149+
return a.Path + b.Path, apath + bpath
150+
}
151+
152+
func newSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
153+
targetQuery := target.RawQuery
154+
director := func(req *http.Request) {
155+
originalHost := req.Host
156+
157+
req.URL.Scheme = target.Scheme
158+
req.URL.Host = target.Host
159+
req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
160+
if targetQuery == "" || req.URL.RawQuery == "" {
161+
req.URL.RawQuery = targetQuery + req.URL.RawQuery
162+
} else {
163+
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
164+
}
165+
req.Host = target.Host
166+
167+
if _, ok := req.Header["User-Agent"]; !ok {
168+
// explicitly disable User-Agent so it's not set to default value
169+
req.Header.Set("User-Agent", "")
170+
}
171+
172+
// From https://github.com/golang/go/pull/36678
173+
prior, ok := req.Header["X-Forwarded-Host"]
174+
omit := ok && prior == nil // nil means don't populate the header
175+
if !omit {
176+
req.Header.Set("X-Forwarded-Host", originalHost)
177+
}
178+
179+
prior, ok = req.Header["X-Forwarded-Proto"]
180+
omit = ok && prior == nil // nil means don't populate the header
181+
if !omit {
182+
if req.TLS == nil {
183+
req.Header.Set("X-Forwarded-Proto", "http")
184+
} else {
185+
req.Header.Set("X-Forwarded-Proto", "https")
186+
}
187+
}
188+
// ReverseProxy will add X-Forwarded-For internally
189+
}
190+
return &httputil.ReverseProxy{Director: director}
191+
}

0 commit comments

Comments
 (0)