package exportproxy import ( "bufio" "context" "encoding/base64" "errors" "fmt" "net" "net/http" "strings" ) func serveHTTP(client net.Conn, config Config, dialer *net.Dialer, resolver *net.Resolver) error { reader := bufio.NewReader(client) request, err := http.ReadRequest(reader) if err != nil { return err } if config.AuthEnabled && !httpAuthorized(request, config) { _, _ = client.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm=\"vocat-export-proxy\"\r\n\r\n")) return errors.New("HTTP proxy authentication required") } if request.Method == http.MethodConnect { ctx, cancel := context.WithTimeout(context.Background(), proxyTimeout) target, err := dialTarget(ctx, request.URL.Host, dialer, resolver) cancel() if err != nil { _, _ = fmt.Fprint(client, "HTTP/1.1 502 Bad Gateway\r\n\r\n") return err } defer target.Close() if _, err := client.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n")); err != nil { return err } if buffered := reader.Buffered(); buffered > 0 { if value, err := reader.Peek(buffered); err == nil { _, _ = target.Write(value) _, _ = reader.Discard(buffered) } } pipe(client, target) return nil } request.Header.Del("Proxy-Authorization") request.Header.Del("Proxy-Connection") request.RequestURI = "" transport := &http.Transport{ DialContext: func(ctx context.Context, _, address string) (net.Conn, error) { return dialTarget(ctx, address, dialer, resolver) }, DisableKeepAlives: true, } response, err := transport.RoundTrip(request) if err != nil { _, _ = fmt.Fprint(client, "HTTP/1.1 502 Bad Gateway\r\n\r\n") return err } defer response.Body.Close() return response.Write(client) } func httpAuthorized(request *http.Request, config Config) bool { header := strings.TrimSpace(strings.TrimPrefix(request.Header.Get("Proxy-Authorization"), "Basic ")) decoded, err := base64.StdEncoding.DecodeString(header) if err != nil { return false } parts := strings.SplitN(string(decoded), ":", 2) return len(parts) == 2 && parts[0] == config.Username && parts[1] == config.Password }