diff --git a/pkg/api/http_client.go b/pkg/api/http_client.go index 43193734..e87ebe8f 100644 --- a/pkg/api/http_client.go +++ b/pkg/api/http_client.go @@ -1,6 +1,7 @@ package api import ( + "context" "fmt" "io" "net" @@ -235,13 +236,14 @@ func (hrt headerRoundTripper) RoundTrip(req *http.Request) (*http.Response, erro } func newUnixDomainSocketRoundTripper(socketPath string) http.RoundTripper { - dial := func(network, addr string) (net.Conn, error) { - return net.Dial("unix", socketPath) + dial := func(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(ctx, "unix", socketPath) } return &http.Transport{ - Dial: dial, - DialTLS: dial, + DialContext: dial, + DialTLSContext: dial, DisableKeepAlives: true, } } diff --git a/pkg/api/http_client_test.go b/pkg/api/http_client_test.go index 557e1c60..38f2868f 100644 --- a/pkg/api/http_client_test.go +++ b/pkg/api/http_client_test.go @@ -4,12 +4,16 @@ import ( "bytes" "fmt" "io" + "net" "net/http" + "os" + "path/filepath" "strings" "testing" "github.com/cli/go-gh/v2/internal/testutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gopkg.in/h2non/gock.v1" ) @@ -321,6 +325,72 @@ func TestNewHTTPClientCheckRedirect(t *testing.T) { }) } +func TestNewHTTPClientSendsHTTPRequestsThroughUnixDomainSocket(t *testing.T) { + t.Parallel() + + // Given a server listening on a unix socket and a client configured to use it + socketPath := serveOnUnixSocket(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "socket server received %s%s", r.Host, r.URL.Path) + })) + client, err := NewHTTPClient(ClientOptions{ + Host: "github.com", + AuthToken: "oauth_token", + UnixDomainSocket: socketPath, + }) + require.NoError(t, err) + + // When the client requests an http URL whose host cannot resolve + res, err := client.Get("http://unresolvable.invalid/user") + + // Then the request is delivered to the socket server + require.NoError(t, err) + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + require.NoError(t, err) + assert.Equal(t, "socket server received unresolvable.invalid/user", string(body)) +} + +func TestNewHTTPClientSendsHTTPSRequestsThroughUnixDomainSocketWithoutTLS(t *testing.T) { + t.Parallel() + + // Given a plain HTTP server listening on a unix socket and a client configured to use it + socketPath := serveOnUnixSocket(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "socket server received %s%s", r.Host, r.URL.Path) + })) + client, err := NewHTTPClient(ClientOptions{ + Host: "github.com", + AuthToken: "oauth_token", + UnixDomainSocket: socketPath, + }) + require.NoError(t, err) + + // When the client requests an https URL whose host cannot resolve + res, err := client.Get("https://unresolvable.invalid/user") + + // Then the request is delivered to the socket server as plain HTTP + require.NoError(t, err) + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + require.NoError(t, err) + assert.Equal(t, "socket server received unresolvable.invalid/user", string(body)) +} + +// serveOnUnixSocket serves handler on a new unix socket and returns its path. +func serveOnUnixSocket(t *testing.T, handler http.Handler) string { + t.Helper() + // t.TempDir() can exceed the 104-byte socket path limit on macOS. + dir, err := os.MkdirTemp("", "gh") + require.NoError(t, err) + t.Cleanup(func() { _ = os.RemoveAll(dir) }) + socketPath := filepath.Join(dir, "gh.sock") + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + server := &http.Server{Handler: handler} + go func() { _ = server.Serve(listener) }() + t.Cleanup(func() { _ = server.Close() }) + return socketPath +} + type tripper struct { roundTrip func(*http.Request) (*http.Response, error) } diff --git a/pkg/x/markdown/accessibility_test.go b/pkg/x/markdown/accessibility_test.go index 5e0ccc42..95294b8e 100644 --- a/pkg/x/markdown/accessibility_test.go +++ b/pkg/x/markdown/accessibility_test.go @@ -192,7 +192,7 @@ func TestAccessibleLightStyleConfigIs4Bit(t *testing.T) { // that are downsampled to 4-bit colors unlike Glamour, which are 8-bit colors. // For more information, https://github.com/alecthomas/chroma/blob/0bf0e9f9ae2a81d463afe769cce01ff821bee3ba/formatters/tty_indexed.go#L32-L44 func validateColors(t *testing.T, v reflect.Value, path string) { - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { if v.IsNil() { return } @@ -212,7 +212,7 @@ func validateColors(t *testing.T, v reflect.Value, path string) { if fieldType.Name == "Chroma" { continue } else if (fieldType.Name == "Color" || fieldType.Name == "BackgroundColor") && - fieldType.Type.Kind() == reflect.Ptr && fieldType.Type.Elem().Kind() == reflect.String { + fieldType.Type.Kind() == reflect.Pointer && fieldType.Type.Elem().Kind() == reflect.String { if field.IsNil() { continue