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
5 changes: 4 additions & 1 deletion proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,14 @@ func (p *Server) forwardRequest(conn net.Conn, req *http.Request, https bool) {
scheme = "https"
}

// Create a new request to the target server
// RawPath preserves the original percent-encoding (e.g. %2F in scoped
// npm package names like /@sentry%2Fbun) so url.URL.String() doesn't
// re-encode reserved characters as their literal form.
targetURL := &url.URL{
Scheme: scheme,
Host: req.Host,
Path: req.URL.Path,
RawPath: req.URL.RawPath,
RawQuery: req.URL.RawQuery,
Comment thread
SasSwart marked this conversation as resolved.
}

Expand Down
23 changes: 23 additions & 0 deletions proxy/proxy_framework_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"log/slog"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"os/user"
Expand Down Expand Up @@ -290,6 +291,28 @@ func (pt *ProxyTest) ExpectAllowedViaProxy(targetURL, expectedBody string) {
require.Equal(pt.t, expectedBody, string(body), "Expected response body does not match")
}

// ExpectRawURI spins up a temporary httptest backend, sends a request through
// the proxy with the given path, and asserts the backend received the expected
// raw URI. Useful for verifying that percent-encoded characters survive forwarding.
func (pt *ProxyTest) ExpectRawURI(requestPath, expectedRawURI string) {
pt.t.Helper()

var receivedRawURI string
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
receivedRawURI = r.RequestURI
w.WriteHeader(http.StatusOK)
}))
defer backend.Close()

resp, err := pt.proxyClient.Get(backend.URL + requestPath)
require.NoError(pt.t, err, "Failed to make request via proxy")
defer resp.Body.Close() //nolint:errcheck

require.Equal(pt.t, http.StatusOK, resp.StatusCode)
require.Equal(pt.t, expectedRawURI, receivedRawURI,
"proxy must preserve raw URI encoding")
}

// ExpectAllowedContainsViaProxy makes a request through the proxy using proxy transport (implicit CONNECT for HTTPS)
// and expects it to be allowed, checking that response contains the given text
func (pt *ProxyTest) ExpectAllowedContainsViaProxy(targetURL, containsText string) {
Expand Down
17 changes: 17 additions & 0 deletions proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ func TestProxyServerBasicHTTP(t *testing.T) {
})
}

// TestProxyServerPercentEncoding verifies that the proxy preserves
// percent-encoded reserved characters when forwarding requests.
func TestProxyServerPercentEncoding(t *testing.T) {
pt := NewProxyTest(t,
WithCertManager(t.TempDir()),
WithAllowedDomain("127.0.0.1"),
).Start()
defer pt.Stop()

t.Run("PercentEncodedSlash", func(t *testing.T) {
pt.ExpectRawURI(
"/npm/npm-all/@sentry%2Fbun",
"/npm/npm-all/@sentry%2Fbun",
)
})
}

// TestProxyServerBasicHTTPS tests basic HTTPS request handling
func TestProxyServerBasicHTTPS(t *testing.T) {
pt := NewProxyTest(t,
Expand Down
Loading