Skip to content

Commit be4c879

Browse files
authored
fix(proxy): handle relative-URL requests in forward proxy mode (#1220)
Resolves #774. When goproxy routes a relative-URL HTTP/1.1 request to NonproxyHandler, reconstruct the absolute URL from the Host header and re-dispatch to the proxy. Return 400 Bad Request if Host is absent, 500 if Host points to the proxy itself (self-address guard to prevent infinite recursion). Add regression tests covering all three paths. Extract duplicated string literals into constants.
1 parent 3982cc5 commit be4c879

2 files changed

Lines changed: 189 additions & 84 deletions

File tree

core/proxy.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/base64"
66
"io"
7+
"net"
78
"net/http"
89
"net/url"
910
"regexp"
@@ -22,6 +23,8 @@ import (
2223

2324
var ProxyAuthorizationHeader string
2425

26+
const errProxyAuthRequired = "407 Proxy authentication required"
27+
2528
// Creates goproxy.ProxyHttpServer and configures it to be used as a proxy for Hoverfly
2629
// goproxy is given handlers that use the Hoverfly request processing
2730
func NewProxy(hoverfly *Hoverfly) *goproxy.ProxyHttpServer {
@@ -30,6 +33,27 @@ func NewProxy(hoverfly *Hoverfly) *goproxy.ProxyHttpServer {
3033
// creating proxy
3134
proxy := goproxy.NewProxyHttpServer()
3235

36+
// Fix #774: HTTP/1.1 clients may send requests with a relative URL (path only) and a Host header.
37+
// goproxy's default NonproxyHandler returns 500 for these. Reconstruct the absolute URL from the
38+
// Host header so the request is processed normally, matching the behaviour of NewWebserverProxy.
39+
//
40+
// Guard: if the Host header targets the proxy's own port the client is connecting directly to
41+
// Hoverfly (not using it as a proxy). In that case keep the original 500 "is a proxy server"
42+
// response — forwarding to ourselves would cause infinite recursion or a panic.
43+
proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
44+
if r.Host == "" {
45+
http.Error(w, "Cannot handle requests without Host header, e.g., HTTP 1.0", http.StatusBadRequest)
46+
return
47+
}
48+
if _, port, err := net.SplitHostPort(r.Host); err == nil && port == hoverfly.Cfg.ProxyPort {
49+
http.Error(w, "This is a proxy server. Does not respond to non-proxy requests.", http.StatusInternalServerError)
50+
return
51+
}
52+
r.URL.Scheme = "http"
53+
r.URL.Host = r.Host
54+
proxy.ServeHTTP(w, r)
55+
})
56+
3357
if hoverfly.Cfg.AuthEnabled {
3458
log.Info("Enabling proxy authentication")
3559
proxyBasicAndBearer(proxy, "hoverfly", func(user, password string) bool {
@@ -201,25 +225,25 @@ func authFromHeader(req *http.Request, basicFunc func(user, passwd string) bool,
201225
authheader := strings.SplitN(headerValue, " ", 2)
202226
req.Header.Del(ProxyAuthorizationHeader)
203227
if len(authheader) != 2 {
204-
return fmt.Errorf("407 Proxy authentication required")
228+
return fmt.Errorf(errProxyAuthRequired)
205229
}
206230
if authheader[0] == "Basic" {
207231
userpassraw, err := base64.StdEncoding.DecodeString(authheader[1])
208232
if err != nil {
209-
return fmt.Errorf("407 Proxy authentication required")
233+
return fmt.Errorf(errProxyAuthRequired)
210234
}
211235
userpass := strings.SplitN(string(userpassraw), ":", 2)
212236
if len(userpass) != 2 {
213-
return fmt.Errorf("407 Proxy authentication required")
237+
return fmt.Errorf(errProxyAuthRequired)
214238
}
215239
result := basicFunc(userpass[0], userpass[1])
216240
if result == false {
217-
return fmt.Errorf("407 Proxy authentication required")
241+
return fmt.Errorf(errProxyAuthRequired)
218242
}
219243
} else if authheader[0] == "Bearer" {
220244
result := bearerFunc(authheader[1])
221245
if result == false {
222-
return fmt.Errorf("407 Proxy authentication required")
246+
return fmt.Errorf(errProxyAuthRequired)
223247
}
224248
} else {
225249
return fmt.Errorf("407 Unknown authentication type `%v`, only `Basic` or `Bearer` are supported", authheader[0])

0 commit comments

Comments
 (0)