Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions core/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/base64"
"io"
"net"
"net/http"
"net/url"
"regexp"
Expand All @@ -22,6 +23,8 @@ import (

var ProxyAuthorizationHeader string

const errProxyAuthRequired = "407 Proxy authentication required"

// Creates goproxy.ProxyHttpServer and configures it to be used as a proxy for Hoverfly
// goproxy is given handlers that use the Hoverfly request processing
func NewProxy(hoverfly *Hoverfly) *goproxy.ProxyHttpServer {
Expand All @@ -30,6 +33,27 @@ func NewProxy(hoverfly *Hoverfly) *goproxy.ProxyHttpServer {
// creating proxy
proxy := goproxy.NewProxyHttpServer()

// Fix #774: HTTP/1.1 clients may send requests with a relative URL (path only) and a Host header.
// goproxy's default NonproxyHandler returns 500 for these. Reconstruct the absolute URL from the
// Host header so the request is processed normally, matching the behaviour of NewWebserverProxy.
//
// Guard: if the Host header targets the proxy's own port the client is connecting directly to
// Hoverfly (not using it as a proxy). In that case keep the original 500 "is a proxy server"
// response — forwarding to ourselves would cause infinite recursion or a panic.
proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it override the default goproxy implementation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it overrides the default goproxy NonproxyHandler, which returns HTTP 500 for all non-proxy requests. The new handler reconstructs the absolute URL from the Host header and re-dispatches the request through proxy.ServeHTTP, enabling HTTP/1.1 clients that send relative URLs to be handled correctly.

The original 500 behavior is preserved only when the Host targets the proxy's own port, to prevent infinite recursion.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! I think this should fix the issue while improving the existing non-proxy request check.

if r.Host == "" {
http.Error(w, "Cannot handle requests without Host header, e.g., HTTP 1.0", http.StatusBadRequest)
return
}
if _, port, err := net.SplitHostPort(r.Host); err == nil && port == hoverfly.Cfg.ProxyPort {
http.Error(w, "This is a proxy server. Does not respond to non-proxy requests.", http.StatusInternalServerError)
return
}
r.URL.Scheme = "http"
r.URL.Host = r.Host
proxy.ServeHTTP(w, r)
})

if hoverfly.Cfg.AuthEnabled {
log.Info("Enabling proxy authentication")
proxyBasicAndBearer(proxy, "hoverfly", func(user, password string) bool {
Expand Down Expand Up @@ -201,25 +225,25 @@ func authFromHeader(req *http.Request, basicFunc func(user, passwd string) bool,
authheader := strings.SplitN(headerValue, " ", 2)
req.Header.Del(ProxyAuthorizationHeader)
if len(authheader) != 2 {
return fmt.Errorf("407 Proxy authentication required")
return fmt.Errorf(errProxyAuthRequired)
}
if authheader[0] == "Basic" {
userpassraw, err := base64.StdEncoding.DecodeString(authheader[1])
if err != nil {
return fmt.Errorf("407 Proxy authentication required")
return fmt.Errorf(errProxyAuthRequired)
}
userpass := strings.SplitN(string(userpassraw), ":", 2)
if len(userpass) != 2 {
return fmt.Errorf("407 Proxy authentication required")
return fmt.Errorf(errProxyAuthRequired)
}
result := basicFunc(userpass[0], userpass[1])
if result == false {
return fmt.Errorf("407 Proxy authentication required")
return fmt.Errorf(errProxyAuthRequired)
}
} else if authheader[0] == "Bearer" {
result := bearerFunc(authheader[1])
if result == false {
return fmt.Errorf("407 Proxy authentication required")
return fmt.Errorf(errProxyAuthRequired)
}
} else {
return fmt.Errorf("407 Unknown authentication type `%v`, only `Basic` or `Bearer` are supported", authheader[0])
Expand Down
Loading
Loading