Fix Unix socket HTTPS/HTTP issue in OpenGraph fetching

Co-authored-by: JasonLovesDoggo <66544866+JasonLovesDoggo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-08-24 03:36:36 +00:00
parent 9a85f0b053
commit ac6101b6bf
5 changed files with 50 additions and 138 deletions
+20 -1
View File
@@ -21,6 +21,24 @@ const (
querySeparatorLength = 1 // Length of "?" for query strings
)
// unixRoundTripper wraps an http.Transport to handle Unix socket requests properly
// by ensuring the URL scheme is set to "http" to avoid TLS issues.
// Based on the UnixRoundTripper implementation in lib/http.go
type unixRoundTripper struct {
Transport *http.Transport
}
// RoundTrip implements the http.RoundTripper interface
func (t *unixRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req = req.Clone(req.Context())
if req.Host == "" {
req.Host = "localhost"
}
req.URL.Host = req.Host
req.URL.Scheme = "http" // Ensure HTTP scheme to avoid "server gave HTTP response to HTTPS client" error
return t.Transport.RoundTrip(req)
}
type OGTagCache struct {
cache store.JSON[map[string]string]
targetURL *url.URL
@@ -69,11 +87,12 @@ func NewOGTagCache(target string, conf config.OpenGraph, backend store.Interface
// Configure custom transport for Unix sockets
if parsedTargetURL.Scheme == "unix" {
socketPath := parsedTargetURL.Path // For unix scheme, path is the socket path
client.Transport = &http.Transport{
transport := &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
}
client.Transport = &unixRoundTripper{Transport: transport}
}
return &OGTagCache{
+4 -4
View File
@@ -99,16 +99,16 @@ func TestNewOGTagCache_UnixSocket(t *testing.T) {
}
// Check if the client transport is configured for Unix sockets
transport, ok := cache.client.Transport.(*http.Transport)
roundTripper, ok := cache.client.Transport.(*unixRoundTripper)
if !ok {
t.Fatalf("expected client transport to be *http.Transport, got %T", cache.client.Transport)
t.Fatalf("expected client transport to be *unixRoundTripper, got %T", cache.client.Transport)
}
if transport.DialContext == nil {
if roundTripper.Transport.DialContext == nil {
t.Fatal("expected client transport DialContext to be non-nil for unix socket")
}
// Attempt a dummy dial to see if it uses the correct path (optional, more involved check)
dummyConn, err := transport.DialContext(context.Background(), "", "")
dummyConn, err := roundTripper.Transport.DialContext(context.Background(), "", "")
if err == nil {
dummyConn.Close()
t.Log("DialContext seems functional, but couldn't verify path without a listener")
-35
View File
@@ -1,35 +0,0 @@
package ogtags
import (
"net/url"
"testing"
"time"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store/memory"
)
// TestUnixSocketHTTPSIssue reproduces the issue where unix socket targets
// might cause HTTPS/HTTP mismatch errors
func TestUnixSocketHTTPSIssue(t *testing.T) {
target := "unix:///var/run/app.sock"
cache := NewOGTagCache(target, config.OpenGraph{
Enabled: true,
TimeToLive: time.Minute,
}, memory.New(t.Context()))
// Test with HTTPS URL (this might be the source of confusion)
httpsURL, _ := url.Parse("https://example.com/test?param=value")
// Get the target URL that would be used for the request
targetURL := cache.getTarget(httpsURL)
t.Logf("Target URL for unix socket: %s", targetURL)
// The target URL should be using http:// scheme, not https://
expected := "http://unix/test?param=value"
if targetURL != expected {
t.Errorf("Expected %s, got %s", expected, targetURL)
}
}
-70
View File
@@ -1,70 +0,0 @@
package ogtags
import (
"context"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"testing"
"time"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store/memory"
)
// TestUnixSocketTLSIssue tries to reproduce the actual "http: server gave HTTP response to HTTPS client" error
func TestUnixSocketTLSIssue(t *testing.T) {
tempDir := t.TempDir()
socketPath := filepath.Join(tempDir, "test.sock")
// Create a simple HTTP server listening on the Unix socket
server := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`<html><head><meta property="og:title" content="Test Title"></head></html>`))
}),
}
// Listen on Unix socket
listener, err := net.Listen("unix", socketPath)
if err != nil {
t.Fatalf("Failed to create Unix socket listener: %v", err)
}
defer os.Remove(socketPath)
defer listener.Close()
// Start the server
go func() {
server.Serve(listener)
}()
defer server.Close()
// Wait a bit for server to start
time.Sleep(100 * time.Millisecond)
// Create OGTagCache with Unix socket target
target := "unix://" + socketPath
cache := NewOGTagCache(target, config.OpenGraph{
Enabled: true,
TimeToLive: time.Minute,
}, memory.New(t.Context()))
// Test with an HTTPS URL (this simulates the original request coming via HTTPS)
httpsURL, _ := url.Parse("https://example.com/test")
// Try to get OG tags - this should work without HTTPS/HTTP errors
ogTags, err := cache.GetOGTags(context.Background(), httpsURL, "example.com")
if err != nil {
// If we get "http: server gave HTTP response to HTTPS client", this is the bug
if err.Error() == "http: server gave HTTP response to HTTPS client" {
t.Errorf("Found the bug: %v", err)
} else {
t.Logf("Got different error: %v", err)
}
} else {
t.Logf("Success: got OG tags: %v", ogTags)
}
}
@@ -15,18 +15,22 @@ import (
"github.com/TecharoHQ/anubis/lib/store/memory"
)
// TestDebugUnixSocketRequests - let's debug exactly what URLs are being constructed
func TestDebugUnixSocketRequests(t *testing.T) {
// TestUnixSocketHTTPSFix tests that the fix prevents "http: server gave HTTP response to HTTPS client" errors
// when using Unix socket targets with HTTPS input URLs
func TestUnixSocketHTTPSFix(t *testing.T) {
tempDir := t.TempDir()
socketPath := filepath.Join(tempDir, "test.sock")
// Create a simple HTTP server listening on the Unix socket that logs requests
// Create a simple HTTP server listening on the Unix socket
server := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Logf("Received request: %s %s", r.Method, r.URL.String())
t.Logf("Request scheme: %s", r.URL.Scheme)
t.Logf("Request host: %s", r.Host)
t.Logf("Is TLS: %v", r.TLS != nil)
// Verify that the request comes in with HTTP (not HTTPS)
if r.URL.Scheme != "" && r.URL.Scheme != "http" {
t.Errorf("Unexpected scheme in request: %s (expected 'http' or empty)", r.URL.Scheme)
}
if r.TLS != nil {
t.Errorf("Request has TLS information when it shouldn't for Unix socket")
}
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
@@ -53,47 +57,41 @@ func TestDebugUnixSocketRequests(t *testing.T) {
// Create OGTagCache with Unix socket target
target := "unix://" + socketPath
t.Logf("Using target: %s", target)
cache := NewOGTagCache(target, config.OpenGraph{
Enabled: true,
TimeToLive: time.Minute,
}, memory.New(t.Context()))
// Test with various URL schemes
// Test cases that previously might have caused the "HTTP response to HTTPS client" error
testCases := []struct {
name string
inputURL string
url string
}{
{"HTTPS URL", "https://example.com/test"},
{"HTTP URL", "http://example.com/test"},
{"HTTPS with port", "https://example.com:8080/test"},
{"HTTPS with port", "https://example.com:443/test"},
{"HTTPS with query", "https://example.com/test?param=value"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
inputURL, _ := url.Parse(tc.inputURL)
t.Logf("Input URL: %s", inputURL.String())
inputURL, _ := url.Parse(tc.url)
// Get the target URL that will be used
targetURL := cache.getTarget(inputURL)
t.Logf("Target URL: %s", targetURL)
// Verify that the target URL uses http scheme
if !strings.HasPrefix(targetURL, "http://unix") {
t.Errorf("Expected target URL to start with 'http://unix', got: %s", targetURL)
}
// Try to get OG tags
// This should succeed without the "server gave HTTP response to HTTPS client" error
ogTags, err := cache.GetOGTags(context.Background(), inputURL, "example.com")
if err != nil {
if strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") {
t.Errorf("BUG FOUND: %v", err)
t.Errorf("Fix did not work: still getting HTTPS/HTTP error: %v", err)
} else {
t.Logf("Different error: %v", err)
// Other errors are acceptable for this test
t.Logf("Got non-HTTPS error (acceptable): %v", err)
}
} else {
t.Logf("Success: got OG tags: %v", ogTags)
// Success case
if ogTags["og:title"] != "Test Title" {
t.Errorf("Expected og:title 'Test Title', got: %v", ogTags["og:title"])
}
t.Logf("Success: got expected og:title = %s", ogTags["og:title"])
}
})
}