mitm/main.go

897 lines
24 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"database/sql"
"encoding/pem"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"github.com/elazarl/goproxy"
_ "github.com/mattn/go-sqlite3" // cgo 版(极致性能)
"golang.org/x/sys/windows/registry"
)
const AppVersion = "2.4"
type Config struct {
DomainsOfInterest []string `toml:"domains_of_interest"`
DictFile string `toml:"dict_file"`
Proxy struct {
Port int `toml:"port"`
} `toml:"proxy"`
Dump struct {
OutputDir string `toml:"output_dir"`
DOIDir string `toml:"DOI_dir"`
Enabled bool `toml:"Enabled"`
} `toml:"dump"`
ASR struct {
ReplacePercentage int `toml:"replace_percentage"`
} `toml:"asr"`
}
type UserData struct {
TxTime time.Time
RxTime time.Time
RequestBody []byte
ModifiedBody []byte
ResponseBody []byte
}
type ProxyServer struct {
config *Config
tlsConfig *tls.Config
proxy *goproxy.ProxyHttpServer
server *http.Server
originalProxy string
dumper *Batcher
verbose bool
debugMode bool
}
func main() {
// Parse command line flags
var testConnectivity = flag.Bool("test", false, "Test proxy connectivity")
var verbose = flag.Bool("v", false, "Verbose mode - log more information")
var debugMode = flag.Bool("d", false, "Debug mode - dump modified requests/responses")
flag.Parse()
// Set console to UTF-8 on Windows to prevent garbled text
if runtime.GOOS == "windows" {
setConsoleUTF8()
}
fmt.Println("MITM Proxy Server v" + AppVersion)
// Load configuration
config, err := loadConfig("config.toml")
if err != nil {
log.Fatalf("Failed to load configuration: %v", err)
}
log.Println(config.String())
fmt.Printf("Reading dictionary from %s...\n", config.DictFile)
dict, err := NewChineseDict(config.DictFile)
if err != nil {
log.Fatalf("Failed to load dictionary: %v", err)
}
Dict = dict
fmt.Printf("Dictionary loaded successfully, size=%d\n", Dict.GetCharacterCount())
fmt.Println("Starting MITM proxy server...")
// Create output directories
if err := os.MkdirAll(config.Dump.OutputDir, 0755); err != nil {
log.Fatalf("Failed to create output directory: %v", err)
}
if config.Dump.DOIDir != "" {
if err := os.MkdirAll(config.Dump.DOIDir, 0755); err != nil {
log.Fatalf("Failed to create DOI directory: %v", err)
}
}
// create db
var batcher *Batcher = nil
if config.Dump.Enabled {
filename := filepath.Join(config.Dump.OutputDir, time.Now().Format("20060102.150405.000000000")+".db")
db, err := sql.Open("sqlite3", "file:"+filename+"?_busy_timeout=5000&_journal_mode=WAL")
if err != nil {
panic(err)
}
defer db.Close()
b, err := NewBatcher(db,
1000, // maxRows一次最多 1000 行
2<<20, // maxBytes~2MB 触发
200*time.Millisecond, // flushEvery最多等 200ms
)
if err != nil {
panic(err)
}
batcher = b
defer batcher.Close()
}
// Create proxy server
proxy, err := NewProxyServer(config, *verbose, *debugMode, batcher)
if err != nil {
log.Fatalf("Failed to create proxy server: %v", err)
}
// Install CA certificate
if err := proxy.installCACert(); err != nil {
log.Printf("Failed to install CA certificate: %v", err)
} else {
fmt.Println("CA certificate installed successfully")
}
// Set system proxy
if err := proxy.setSystemProxy(); err != nil {
log.Fatalf("Failed to set system proxy: %v", err)
}
fmt.Printf("System proxy set to 127.0.0.1:%d\n", config.Proxy.Port)
// Verify proxy settings were applied
if err := proxy.verifyProxySettings(); err != nil {
fmt.Printf("Warning: Could not verify proxy settings: %v\n", err)
} else {
fmt.Printf("Proxy settings verified in Windows registry\n")
}
// Show current proxy configuration for debugging
proxy.showProxyConfiguration()
// Start proxy server
serverStarted := make(chan error, 1)
go func() {
fmt.Printf("Starting proxy server on port %d...\n", config.Proxy.Port)
if err := proxy.Start(); err != nil && err != http.ErrServerClosed {
serverStarted <- err
return
}
serverStarted <- nil
}()
// Wait for server to start or fail
fmt.Printf("Waiting for server to start...\n")
select {
case err := <-serverStarted:
if err != nil {
log.Fatalf("Failed to start proxy server: %v", err)
}
fmt.Printf("Proxy server successfully started on port %d\n", config.Proxy.Port)
case <-time.After(5 * time.Second):
fmt.Printf("Proxy server appears to be starting (no immediate errors)\n")
}
// Run full connectivity test if requested
if *testConnectivity {
proxyAddr := fmt.Sprintf("127.0.0.1:%d", config.Proxy.Port)
testProxyConnectivity(proxyAddr)
}
// Wait for interrupt signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
fmt.Println("\nShutting down proxy server...")
proxy.Shutdown()
fmt.Println("Proxy server closed")
if batcher != nil {
if err := batcher.Err(); err != nil {
fmt.Println("background error:", err)
}
}
}
func loadConfig(filename string) (*Config, error) {
return parseConfig(filename)
}
func NewProxyServer(config *Config, verbose bool, debugMode bool, batcher *Batcher) (*ProxyServer, error) {
// Load hardcoded P12 certificate for MITM
tlsConfig, err := loadHardcodedCertificate()
if err != nil {
return nil, fmt.Errorf("failed to load certificate: %v", err)
}
// Create goproxy instance
goProxy := goproxy.NewProxyHttpServer()
goProxy.Verbose = verbose
ps := &ProxyServer{
config: config,
tlsConfig: tlsConfig,
proxy: goProxy,
verbose: verbose,
debugMode: debugMode,
dumper: batcher,
}
// Configure MITM for HTTPS traffic
ps.setupMITM()
// Setup request/response handlers
ps.setupHandlers()
// Create HTTP server
ps.server = &http.Server{
Addr: fmt.Sprintf(":%d", config.Proxy.Port),
Handler: ps.proxy,
ErrorLog: log.New(os.Stdout, "GOPROXY-SERVER: ", log.LstdFlags),
}
return ps, nil
}
// setupMITM configures HTTPS MITM using goproxy
func (p *ProxyServer) setupMITM() {
// Set the CA certificate for MITM
if len(p.tlsConfig.Certificates) > 0 {
cert := p.tlsConfig.Certificates[0]
goproxy.GoproxyCa = cert
goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&cert)}
}
// Enable MITM for all HTTPS connections
p.proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
}
// setupHandlers configures request/response handlers for logging and filtering
func (p *ProxyServer) setupHandlers() {
// Add health check endpoint
p.proxy.OnRequest().DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
if r.Host == "127.0.0.1:"+fmt.Sprintf("%d", p.config.Proxy.Port) && r.URL.Path == "/proxy-health" {
return r, goproxy.NewResponse(r, goproxy.ContentTypeText, http.StatusOK, "MITM Proxy is running")
}
return r, nil
})
// Log all HTTP requests and capture request body
p.proxy.OnRequest().DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
if !((p.isDomainOfInterest(r.Host) && p.debugMode) || p.config.Dump.Enabled) {
return r, nil
}
// Read request body once and recreate it for both dumping and forwarding
if r.Body != nil {
reqBody, err := io.ReadAll(r.Body)
if err != nil {
log.Printf("Failed to read request body: %v", err)
return r, nil
}
r.Body.Close()
var newReqBody []byte = nil
if p.isDomainOfInterest(r.Host) {
newReqBody, err = asrResultObfuscate(r, reqBody, p.config.ASR.ReplacePercentage)
if err != nil && err.Error() != "not an asr request" {
log.Printf("Failed to obfuscate request body: %v", err)
}
if !p.debugMode && err == nil {
log.Println("[INFO] ASR Request Body Modified")
}
}
if newReqBody != nil {
r.Body = io.NopCloser(bytes.NewReader(newReqBody))
r.ContentLength = int64(len(newReqBody))
} else {
// Recreate the request body so it can be forwarded to the server
r.Body = io.NopCloser(bytes.NewReader(reqBody))
r.ContentLength = int64(len(reqBody))
}
// Store request body in context for later use in response handler
ctx.UserData = UserData{
TxTime: time.Now(),
RequestBody: reqBody,
ModifiedBody: newReqBody,
}
} else {
ctx.UserData = UserData{
TxTime: time.Now(),
}
}
return r, nil
})
// Log all HTTP responses and dump traffic
p.proxy.OnResponse().DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
if !p.debugMode && !p.config.Dump.Enabled {
return r
}
// Get request body from context (if available)
var userData UserData
if ctx.UserData != nil {
if u, ok := ctx.UserData.(UserData); ok {
u.RxTime = time.Now()
userData = u
} else {
// There is no userdata, which mean the traffic should not be captured
return r
}
}
if isWebSocketUpgradeResponse(r) {
if p.verbose || p.debugMode {
fmt.Printf(
"[%s][INFO][Interest=%v] WebSocket upgrade detected, leaving stream untouched: %s\n",
time.Now().Format("20060102T15:04:05.000000"),
p.isDomainOfInterest(ctx.Req.Host),
ctx.Req.URL.String(),
)
}
return r
}
if p.verbose || (p.debugMode && p.isDomainOfInterest(ctx.Req.Host)) {
fmt.Printf(
"[%s][INFO][Interest=%v] HTTP Response: %s %s\n",
time.Now().Format("20060102T15:04:05.000000"),
p.isDomainOfInterest(ctx.Req.Host),
r.Status,
ctx.Req.URL.String(),
)
}
if r != nil {
// Read response body once and recreate it for both dumping and returning
if r.Body != nil {
respBody, err := io.ReadAll(r.Body)
if err != nil {
log.Printf("Failed to read response body: %v", err)
return r
}
r.Body.Close()
// Recreate the response body so it can be returned to client
r.Body = io.NopCloser(bytes.NewReader(respBody))
r.ContentLength = int64(len(respBody))
userData.ResponseBody = respBody
}
}
// Dump traffic to file with both request and response bodies
// Only dump if verbose mode is enabled OR if the request was modified
if p.debugMode {
p.dumpHTTPTrafficWithBodies(ctx.Req, r, userData)
}
if p.config.Dump.Enabled {
p.dumpHTTPTrafficWithBodiesToSQL(ctx.Req, r, userData)
}
return r
})
}
func (p *ProxyServer) Start() error {
fmt.Printf("Starting HTTP server on %s\n", p.server.Addr)
// Test if port is available
listener, err := net.Listen("tcp", p.server.Addr)
if err != nil {
return fmt.Errorf("failed to bind to port %s: %v", p.server.Addr, err)
}
fmt.Printf("Successfully bound to port %s\n", p.server.Addr)
// Start serving with our listener
return p.server.Serve(listener)
}
func (p *ProxyServer) Shutdown() {
// Restore system proxy settings
if err := p.restoreSystemProxy(); err != nil {
log.Printf("Failed to restore system proxy: %v", err)
} else {
fmt.Println("System proxy restored")
}
// Close HTTP server
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
p.server.Shutdown(ctx)
}
// dumpHTTPTrafficWithBodies dumps HTTP request and response with both bodies to file
// It now should only be called in debug mode. Logging the traffic that is modified
func (p *ProxyServer) dumpHTTPTrafficWithBodies(req *http.Request, resp *http.Response, userData UserData) {
file, err := os.Create(p.getFilePath(req))
if err != nil {
log.Printf("Failed to create dump file: %v", err)
return
}
defer file.Close()
// Write request information
fmt.Fprintf(file, "=== REQUEST ===\n")
fmt.Fprintf(file, "%s %s %s\n", req.Method, req.URL.String(), req.Proto)
fmt.Fprintf(file, "Host: %s\n", req.Host)
// Write all request headers
for name, values := range req.Header {
for _, value := range values {
fmt.Fprintf(file, "%s: %s\n", name, value)
}
}
fmt.Fprintf(file, "\n")
// Write request body
if len(userData.RequestBody) > 0 {
fmt.Fprintf(file, "%s\n", string(userData.RequestBody))
}
if userData.ModifiedBody != nil {
fmt.Fprintf(file, "\n=== MODIFIED REQUEST BODY ===\n")
fmt.Fprintf(file, "%s\n", string(userData.ModifiedBody))
}
// Write response information
if resp != nil {
fmt.Fprintf(file, "\n=== RESPONSE ===\n")
fmt.Fprintf(file, "%s %s\n", resp.Proto, resp.Status)
// Write all response headers
for name, values := range resp.Header {
for _, value := range values {
fmt.Fprintf(file, "%s: %s\n", name, value)
}
}
fmt.Fprintf(file, "\n")
// Write response body
if len(userData.ResponseBody) > 0 {
fmt.Fprintf(file, "%s\n", string(userData.ResponseBody))
}
}
}
// dumpHTTPTrafficWithBodiesToSQL logs the traffic to SQLite DB. It only writes what is actually sent,
// which mean, if a request is modified, then it will write the modified request.
func (p *ProxyServer) dumpHTTPTrafficWithBodiesToSQL(req *http.Request, resp *http.Response, userData UserData) {
record := LogRow{
TSns: time.Now().UnixNano(),
TxTime: userData.TxTime,
Proto: req.Proto,
Method: req.Method,
URL: req.URL.String(),
Modified: userData.ModifiedBody != nil,
RxTime: time.Now(),
}
if record.Modified {
record.TxBody = string(userData.ModifiedBody)
} else {
record.TxBody = string(userData.RequestBody)
}
// Write all request headers
for name, values := range req.Header {
for _, value := range values {
record.TxHeader += fmt.Sprintf("%s: %s\n", name, value)
}
}
// Write response information
if resp != nil {
record.Status = resp.StatusCode
// Write all response headers
for name, values := range resp.Header {
for _, value := range values {
record.RxHeader += fmt.Sprintf("%s: %s\n", name, value)
}
}
record.RxBody = string(userData.ResponseBody)
}
if err := p.dumper.Write(record); err != nil {
log.Printf("Failed to write record: %v", err)
}
}
func (p *ProxyServer) getFilePath(req *http.Request) string {
timestamp := time.Now().Format("20060102.150405.000000000")
filename := fmt.Sprintf("%s_%s.txt", timestamp, sanitizeFilename(req.URL.String()))
if p.isDomainOfInterest(req.Host) {
return filepath.Join(p.config.Dump.DOIDir, filename)
}
return filepath.Join(p.config.Dump.OutputDir, filename)
}
// Keep the isDomainOfInterest method as it's still needed
func (p *ProxyServer) isDomainOfInterest(host string) bool {
// Remove port number
if colonIndex := strings.Index(host, ":"); colonIndex != -1 {
host = host[:colonIndex]
}
for _, domain := range p.config.DomainsOfInterest {
if strings.Contains(host, domain) {
return true
}
}
return false
}
// sanitizeFilename replaces unsafe characters for file names
func sanitizeFilename(filename string) string {
// Replace unsafe characters
replacer := strings.NewReplacer(
":", "_",
"/", "_",
"\\", "_",
"*", "_",
"?", "_",
"\"", "_",
"<", "_",
">", "_",
"|", "_",
)
s := replacer.Replace(filename)
if len(s) > 80 {
return s[:80]
}
return s
}
func isWebSocketUpgradeResponse(resp *http.Response) bool {
if resp == nil {
return false
}
if resp.StatusCode != http.StatusSwitchingProtocols {
return false
}
return headerContainsValue(resp.Header, "Connection", "Upgrade") &&
headerContainsValue(resp.Header, "Upgrade", "websocket")
}
func headerContainsValue(header http.Header, name, value string) bool {
for _, v := range header.Values(name) {
for _, s := range strings.Split(v, ",") {
if strings.EqualFold(value, strings.TrimSpace(s)) {
return true
}
}
}
return false
}
func (p *ProxyServer) setSystemProxy() error {
// Get current proxy settings
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE)
if err != nil {
return err
}
defer k.Close()
// Read current ProxyServer settings
currentProxy, _, err := k.GetStringValue("ProxyServer")
if err == nil {
p.originalProxy = currentProxy
}
// Set new proxy
k, err = registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.SET_VALUE)
if err != nil {
return err
}
defer k.Close()
proxyAddr := fmt.Sprintf("127.0.0.1:%d", p.config.Proxy.Port)
// Enable proxy
if err := k.SetDWordValue("ProxyEnable", 1); err != nil {
return err
}
// Set proxy server address for BOTH HTTP and HTTPS
// Format: "http=proxy:port;https=proxy:port" or just "proxy:port" for both
if err := k.SetStringValue("ProxyServer", proxyAddr); err != nil {
return err
}
// Ensure no proxy bypass for local addresses
if err := k.SetStringValue("ProxyOverride", "<local>"); err != nil {
// This is not critical, continue anyway
fmt.Printf("Warning: Could not set ProxyOverride: %v\n", err)
}
// Refresh IE settings and notify system
return p.refreshIESettings()
}
func (p *ProxyServer) restoreSystemProxy() error {
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.SET_VALUE)
if err != nil {
return err
}
defer k.Close()
// Disable proxy
if err := k.SetDWordValue("ProxyEnable", 0); err != nil {
return err
}
// Restore original proxy settings
if p.originalProxy != "" {
if err := k.SetStringValue("ProxyServer", p.originalProxy); err != nil {
return err
}
}
// Refresh IE settings
return p.refreshIESettings()
}
func (p *ProxyServer) refreshIESettings() error {
// Multiple methods to ensure Windows recognizes the proxy change
// Method 1: Reset WinHTTP proxy
cmd1 := exec.Command("cmd", "/c", "netsh winhttp reset proxy")
if err := cmd1.Run(); err != nil {
fmt.Printf("Warning: netsh winhttp reset failed: %v\n", err)
}
// Method 2: Import IE proxy settings to WinHTTP
cmd2 := exec.Command("cmd", "/c", "netsh winhttp import proxy source=ie")
if err := cmd2.Run(); err != nil {
fmt.Printf("Warning: netsh winhttp import failed: %v\n", err)
}
// Method 3: Force refresh Internet Options
cmd3 := exec.Command("cmd", "/c", "rundll32.exe inetcpl.cpl,ClearMyTracksByProcess 8")
if err := cmd3.Run(); err != nil {
fmt.Printf("Warning: Internet Options refresh failed: %v\n", err)
}
return nil
}
func (p *ProxyServer) verifyProxySettings() error {
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE)
if err != nil {
return fmt.Errorf("failed to open registry key: %v", err)
}
defer k.Close()
// Check if proxy is enabled
proxyEnable, _, err := k.GetIntegerValue("ProxyEnable")
if err != nil {
return fmt.Errorf("failed to read ProxyEnable: %v", err)
}
if proxyEnable != 1 {
return fmt.Errorf("proxy is not enabled (ProxyEnable = %d)", proxyEnable)
}
// Check proxy server setting
proxyServer, _, err := k.GetStringValue("ProxyServer")
if err != nil {
return fmt.Errorf("failed to read ProxyServer: %v", err)
}
expectedProxy := fmt.Sprintf("127.0.0.1:%d", p.config.Proxy.Port)
if proxyServer != expectedProxy {
return fmt.Errorf("proxy server mismatch: expected %s, got %s", expectedProxy, proxyServer)
}
return nil
}
func (p *ProxyServer) showProxyConfiguration() {
fmt.Printf("Current Proxy Configuration:\n")
// Show Windows proxy settings
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE)
if err != nil {
fmt.Printf("Could not read proxy settings: %v\n", err)
return
}
defer k.Close()
if proxyEnable, _, err := k.GetIntegerValue("ProxyEnable"); err == nil {
fmt.Printf(" ProxyEnable: %d\n", proxyEnable)
}
if proxyServer, _, err := k.GetStringValue("ProxyServer"); err == nil {
fmt.Printf(" ProxyServer: %s\n", proxyServer)
}
if proxyOverride, _, err := k.GetStringValue("ProxyOverride"); err == nil {
fmt.Printf(" ProxyOverride: %s\n", proxyOverride)
}
// Show WinHTTP proxy settings
fmt.Printf(" WinHTTP Proxy Settings:\n")
cmd := exec.Command("cmd", "/c", "netsh winhttp show proxy")
if output, err := cmd.CombinedOutput(); err == nil {
fmt.Printf(" %s\n", string(output))
} else {
fmt.Printf(" ❌ Could not get WinHTTP proxy settings: %v\n", err)
}
}
func (p *ProxyServer) installCACert() error {
// Use hardcoded CA certificate data
certData := []byte(getHardcodedCACert())
// Parse certificate
var cert *x509.Certificate
var err error
// Try parsing DER format directly
cert, err = x509.ParseCertificate(certData)
if err != nil {
// Try parsing PEM format
block, _ := pem.Decode(certData)
if block == nil {
return fmt.Errorf("unable to parse certificate file")
}
cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return err
}
}
// Write certificate to temporary file
tempFile, err := os.CreateTemp("", "ca_cert_*.crt")
if err != nil {
return err
}
tempFileName := tempFile.Name()
defer os.Remove(tempFileName)
if err := pem.Encode(tempFile, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
}); err != nil {
tempFile.Close()
return err
}
// Close the file before certutil accesses it to avoid sharing violation
tempFile.Close()
// Use certutil to install certificate to trusted root certificate authorities
cmd := exec.Command("certutil", "-addstore", "-f", "Root", tempFileName)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to install certificate: %v, output: %s", err, string(output))
}
return nil
}
// setConsoleUTF8 sets the Windows console to UTF-8 code page to prevent garbled text
func setConsoleUTF8() {
// Set console input and output code page to UTF-8 (65001)
cmd := exec.Command("cmd", "/c", "chcp 65001 >nul")
cmd.Run()
}
// testProxyConnectivity tests the proxy with simple HTTP and HTTPS requests
func testProxyConnectivity(proxyAddr string) {
fmt.Println("Testing proxy connectivity...")
// Create proxy URL
proxyURL, err := url.Parse(fmt.Sprintf("http://%s", proxyAddr))
if err != nil {
fmt.Printf("Failed to parse proxy URL: %v\n", err)
return
}
// Create HTTP client with proxy
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
// Test HTTP request
fmt.Println("Testing HTTP request...")
testHTTPRequest(client, "http://httpbin.org/ip")
// Test HTTPS request
fmt.Println("Testing HTTPS request...")
testHTTPSRequest(client, "https://httpbin.org/ip")
// Test redirects (should be transparent)
fmt.Println("Testing HTTP redirect...")
testRedirectTransparency(client, "http://httpbin.org/redirect/1")
// Test domain that might cause issues
fmt.Println("Testing Google...")
testHTTPSRequest(client, "https://www.google.com")
}
func testHTTPRequest(client *http.Client, testURL string) {
resp, err := client.Get(testURL)
if err != nil {
fmt.Printf("HTTP test failed: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Failed to read HTTP response: %v\n", err)
return
}
fmt.Printf("HTTP test successful: %s\n", resp.Status)
fmt.Printf(" Response: %s\n", string(body)[:min(len(body), 200)])
}
func testHTTPSRequest(client *http.Client, testURL string) {
resp, err := client.Get(testURL)
if err != nil {
fmt.Printf("HTTPS test failed: %v\n", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Failed to read HTTPS response: %v\n", err)
return
}
fmt.Printf("HTTPS test successful: %s\n", resp.Status)
fmt.Printf(" Response: %s\n", string(body)[:min(len(body), 200)])
}
func testRedirectTransparency(client *http.Client, testURL string) {
// Create a client that doesn't follow redirects to test transparency
testClient := &http.Client{
Transport: client.Transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Timeout: client.Timeout,
}
resp, err := testClient.Get(testURL)
if err != nil {
fmt.Printf("Redirect transparency test failed: %v\n", err)
return
}
defer resp.Body.Close()
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
location := resp.Header.Get("Location")
fmt.Printf("Redirect transparency test successful: %s -> %s\n", resp.Status, location)
} else {
fmt.Printf("Expected redirect, got: %s (this may be normal)\n", resp.Status)
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}