An improved AquaProxy fork for servers with better experience
proxy legacy
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Separate stuff into .go files, fix `go build`, webUI now serves /cert.cer

+234 -216
+3
go.mod
··· 1 + module tangled.org/nilfinx.tngl.sh/liquidproxy 2 + 3 + go 1.24.6
+9
main.go
··· 1 + package main 2 + 3 + import ( 4 + src "tangled.org/nilfinx.tngl.sh/liquidproxy/src" 5 + ) 6 + 7 + func main() { 8 + src.Run() 9 + }
-3
src/go.mod
··· 1 - module tangled.org/nilfinx.tngl.sh/aquaproxy 2 - 3 - go 1.24.6
+214
src/http-helper.go
··· 1 + package liquidproxy 2 + 3 + import ( 4 + "bufio" 5 + "fmt" 6 + "log" 7 + "net" 8 + "net/http" 9 + "net/url" 10 + "os" 11 + "strings" 12 + ) 13 + 14 + // checkRedirect checks if the request URL matches any redirect rules and returns the target URL 15 + func checkRedirect(reqURL *url.URL) (*url.URL, bool) { 16 + redirectMutex.RLock() 17 + defer redirectMutex.RUnlock() 18 + 19 + // Check if domain has any redirect rules 20 + rules, exists := redirectRules[reqURL.Host] 21 + if !exists { 22 + return nil, false 23 + } 24 + 25 + // Build the full URL string for comparison 26 + fullURL := reqURL.String() 27 + 28 + // Check each rule for the domain 29 + for _, rule := range rules { 30 + fromPrefix := rule.fromURL.String() 31 + if strings.HasPrefix(fullURL, fromPrefix) { 32 + // Apply the redirect, preserving the path suffix 33 + suffix := strings.TrimPrefix(fullURL, fromPrefix) 34 + 35 + // Parse the target URL and append the suffix properly 36 + targetURL, _ := url.Parse(rule.toURL.String()) 37 + if targetURL != nil { 38 + // If suffix contains a query string, handle it properly 39 + if idx := strings.Index(suffix, "?"); idx >= 0 { 40 + targetURL.Path = targetURL.Path + suffix[:idx] 41 + targetURL.RawQuery = suffix[idx+1:] 42 + } else { 43 + targetURL.Path = targetURL.Path + suffix 44 + } 45 + } 46 + return targetURL, true 47 + } 48 + } 49 + 50 + return nil, false 51 + } 52 + 53 + // loadRedirectRules loads URL redirect rules from redirects.txt 54 + func loadRedirectRules() error { 55 + redirectFile := "redirects.txt" 56 + 57 + // Check if file exists 58 + if _, err := os.Stat(redirectFile); os.IsNotExist(err) { 59 + log.Println("Warning: no redirects.txt file found") 60 + return nil 61 + } 62 + 63 + file, err := os.Open(redirectFile) 64 + if err != nil { 65 + return fmt.Errorf("failed to open redirects file: %w", err) 66 + } 67 + defer file.Close() 68 + 69 + scanner := bufio.NewScanner(file) 70 + lineNum := 0 71 + var fromURL *url.URL 72 + 73 + for scanner.Scan() { 74 + lineNum++ 75 + line := strings.TrimSpace(scanner.Text()) 76 + 77 + // Skip empty lines 78 + if line == "" { 79 + continue 80 + } 81 + 82 + // Parse URL 83 + u, err := url.Parse(line) 84 + if err != nil { 85 + log.Printf("Warning: Invalid URL on line %d: %s", lineNum, line) 86 + continue 87 + } 88 + 89 + // Ensure URL has a scheme 90 + if u.Scheme == "" { 91 + log.Printf("Warning: URL missing scheme on line %d: %s", lineNum, line) 92 + continue 93 + } 94 + 95 + if fromURL == nil { 96 + // This is a "from" URL 97 + fromURL = u 98 + } else { 99 + // This is a "to" URL, create the redirect rule 100 + rule := redirectRule{ 101 + fromURL: fromURL, 102 + toURL: u, 103 + } 104 + 105 + // Extract domain from fromURL 106 + domain := fromURL.Host 107 + 108 + redirectMutex.Lock() 109 + redirectRules[domain] = append(redirectRules[domain], rule) 110 + redirectDomains[domain] = true 111 + redirectMutex.Unlock() 112 + 113 + // Reset for next pair 114 + fromURL = nil 115 + } 116 + } 117 + 118 + if err := scanner.Err(); err != nil { 119 + return fmt.Errorf("error reading redirects file: %w", err) 120 + } 121 + 122 + if fromURL != nil { 123 + log.Printf("Warning: Incomplete redirect rule (missing target URL) for: %s", fromURL.String()) 124 + } 125 + 126 + return nil 127 + } 128 + 129 + // loadExclusionRules loads URLs to never MITM from no-mitm.txt 130 + func loadExclusionRules() error { 131 + exclusionFile := "no-mitm.txt" 132 + 133 + // Check if file exists 134 + if _, err := os.Stat(exclusionFile); os.IsNotExist(err) { 135 + log.Println("Warning: no no-mitm.txt file found") 136 + return nil 137 + } 138 + 139 + file, err := os.Open(exclusionFile) 140 + if err != nil { 141 + return fmt.Errorf("failed to open exclusion file: %w", err) 142 + } 143 + defer file.Close() 144 + 145 + scanner := bufio.NewScanner(file) 146 + lineNum := 0 147 + 148 + for scanner.Scan() { 149 + lineNum++ 150 + line := strings.TrimSpace(scanner.Text()) 151 + 152 + // Skip empty lines and comments 153 + if line == "" || strings.HasPrefix(line, "#") { 154 + continue 155 + } 156 + 157 + // Parse URL or domain 158 + if strings.Contains(line, "://") { 159 + // It's a full URL, extract the domain 160 + u, err := url.Parse(line) 161 + if err != nil { 162 + log.Printf("Warning: Invalid URL on line %d: %s", lineNum, line) 163 + continue 164 + } 165 + 166 + if u.Host != "" { 167 + excludedMutex.Lock() 168 + excludedDomains[u.Host] = true 169 + excludedMutex.Unlock() 170 + log.Printf("Excluding domain from MITM: %s", u.Host) 171 + } 172 + } else { 173 + // It's just a domain 174 + domain := line 175 + // Remove port if present 176 + if h, _, err := net.SplitHostPort(domain); err == nil { 177 + domain = h 178 + } 179 + 180 + excludedMutex.Lock() 181 + excludedDomains[domain] = true 182 + excludedMutex.Unlock() 183 + log.Printf("Excluding domain from MITM: %s", domain) 184 + } 185 + } 186 + 187 + if err := scanner.Err(); err != nil { 188 + return fmt.Errorf("error reading exclusion file: %w", err) 189 + } 190 + 191 + return nil 192 + } 193 + 194 + // responseTracker tracks response status and completion 195 + type responseTracker struct { 196 + http.ResponseWriter 197 + reqID string 198 + url string 199 + status int 200 + wroteHeader bool 201 + } 202 + 203 + func (rw *responseTracker) WriteHeader(statusCode int) { 204 + rw.wroteHeader = true 205 + rw.status = statusCode 206 + rw.ResponseWriter.WriteHeader(statusCode) 207 + } 208 + 209 + func (rw *responseTracker) Write(b []byte) (int, error) { 210 + if !rw.wroteHeader { 211 + rw.WriteHeader(http.StatusOK) 212 + } 213 + return rw.ResponseWriter.Write(b) 214 + }
+1 -206
src/http.go
··· 1 - package main 1 + package liquidproxy 2 2 3 3 import ( 4 4 "bufio" ··· 13 13 "net" 14 14 "net/http" 15 15 "net/http/httputil" 16 - "net/url" 17 - "os" 18 16 "strconv" 19 - "strings" 20 17 "time" 21 18 ) 22 19 23 - // checkRedirect checks if the request URL matches any redirect rules and returns the target URL 24 - func checkRedirect(reqURL *url.URL) (*url.URL, bool) { 25 - redirectMutex.RLock() 26 - defer redirectMutex.RUnlock() 27 - 28 - // Check if domain has any redirect rules 29 - rules, exists := redirectRules[reqURL.Host] 30 - if !exists { 31 - return nil, false 32 - } 33 - 34 - // Build the full URL string for comparison 35 - fullURL := reqURL.String() 36 - 37 - // Check each rule for the domain 38 - for _, rule := range rules { 39 - fromPrefix := rule.fromURL.String() 40 - if strings.HasPrefix(fullURL, fromPrefix) { 41 - // Apply the redirect, preserving the path suffix 42 - suffix := strings.TrimPrefix(fullURL, fromPrefix) 43 - 44 - // Parse the target URL and append the suffix properly 45 - targetURL, _ := url.Parse(rule.toURL.String()) 46 - if targetURL != nil { 47 - // If suffix contains a query string, handle it properly 48 - if idx := strings.Index(suffix, "?"); idx >= 0 { 49 - targetURL.Path = targetURL.Path + suffix[:idx] 50 - targetURL.RawQuery = suffix[idx+1:] 51 - } else { 52 - targetURL.Path = targetURL.Path + suffix 53 - } 54 - } 55 - return targetURL, true 56 - } 57 - } 58 - 59 - return nil, false 60 - } 61 - 62 - // loadRedirectRules loads URL redirect rules from redirects.txt 63 - func loadRedirectRules() error { 64 - redirectFile := "redirects.txt" 65 - 66 - // Check if file exists 67 - if _, err := os.Stat(redirectFile); os.IsNotExist(err) { 68 - log.Println("Warning: no redirects.txt file found") 69 - return nil 70 - } 71 - 72 - file, err := os.Open(redirectFile) 73 - if err != nil { 74 - return fmt.Errorf("failed to open redirects file: %w", err) 75 - } 76 - defer file.Close() 77 - 78 - scanner := bufio.NewScanner(file) 79 - lineNum := 0 80 - var fromURL *url.URL 81 - 82 - for scanner.Scan() { 83 - lineNum++ 84 - line := strings.TrimSpace(scanner.Text()) 85 - 86 - // Skip empty lines 87 - if line == "" { 88 - continue 89 - } 90 - 91 - // Parse URL 92 - u, err := url.Parse(line) 93 - if err != nil { 94 - log.Printf("Warning: Invalid URL on line %d: %s", lineNum, line) 95 - continue 96 - } 97 - 98 - // Ensure URL has a scheme 99 - if u.Scheme == "" { 100 - log.Printf("Warning: URL missing scheme on line %d: %s", lineNum, line) 101 - continue 102 - } 103 - 104 - if fromURL == nil { 105 - // This is a "from" URL 106 - fromURL = u 107 - } else { 108 - // This is a "to" URL, create the redirect rule 109 - rule := redirectRule{ 110 - fromURL: fromURL, 111 - toURL: u, 112 - } 113 - 114 - // Extract domain from fromURL 115 - domain := fromURL.Host 116 - 117 - redirectMutex.Lock() 118 - redirectRules[domain] = append(redirectRules[domain], rule) 119 - redirectDomains[domain] = true 120 - redirectMutex.Unlock() 121 - 122 - // Reset for next pair 123 - fromURL = nil 124 - } 125 - } 126 - 127 - if err := scanner.Err(); err != nil { 128 - return fmt.Errorf("error reading redirects file: %w", err) 129 - } 130 - 131 - if fromURL != nil { 132 - log.Printf("Warning: Incomplete redirect rule (missing target URL) for: %s", fromURL.String()) 133 - } 134 - 135 - return nil 136 - } 137 - 138 - // loadExclusionRules loads URLs to never MITM from no-mitm.txt 139 - func loadExclusionRules() error { 140 - exclusionFile := "no-mitm.txt" 141 - 142 - // Check if file exists 143 - if _, err := os.Stat(exclusionFile); os.IsNotExist(err) { 144 - log.Println("Warning: no no-mitm.txt file found") 145 - return nil 146 - } 147 - 148 - file, err := os.Open(exclusionFile) 149 - if err != nil { 150 - return fmt.Errorf("failed to open exclusion file: %w", err) 151 - } 152 - defer file.Close() 153 - 154 - scanner := bufio.NewScanner(file) 155 - lineNum := 0 156 - 157 - for scanner.Scan() { 158 - lineNum++ 159 - line := strings.TrimSpace(scanner.Text()) 160 - 161 - // Skip empty lines and comments 162 - if line == "" || strings.HasPrefix(line, "#") { 163 - continue 164 - } 165 - 166 - // Parse URL or domain 167 - if strings.Contains(line, "://") { 168 - // It's a full URL, extract the domain 169 - u, err := url.Parse(line) 170 - if err != nil { 171 - log.Printf("Warning: Invalid URL on line %d: %s", lineNum, line) 172 - continue 173 - } 174 - 175 - if u.Host != "" { 176 - excludedMutex.Lock() 177 - excludedDomains[u.Host] = true 178 - excludedMutex.Unlock() 179 - log.Printf("Excluding domain from MITM: %s", u.Host) 180 - } 181 - } else { 182 - // It's just a domain 183 - domain := line 184 - // Remove port if present 185 - if h, _, err := net.SplitHostPort(domain); err == nil { 186 - domain = h 187 - } 188 - 189 - excludedMutex.Lock() 190 - excludedDomains[domain] = true 191 - excludedMutex.Unlock() 192 - log.Printf("Excluding domain from MITM: %s", domain) 193 - } 194 - } 195 - 196 - if err := scanner.Err(); err != nil { 197 - return fmt.Errorf("error reading exclusion file: %w", err) 198 - } 199 - 200 - return nil 201 - } 202 - 203 20 func httpMain(systemRoots *x509.CertPool, ca tls.Certificate) { 204 21 // Load redirect rules 205 22 if err := loadRedirectRules(); err != nil { ··· 277 94 278 95 upstream.ServeHTTP(rw, r) 279 96 }) 280 - } 281 - 282 - // responseTracker tracks response status and completion 283 - type responseTracker struct { 284 - http.ResponseWriter 285 - reqID string 286 - url string 287 - status int 288 - wroteHeader bool 289 - } 290 - 291 - func (rw *responseTracker) WriteHeader(statusCode int) { 292 - rw.wroteHeader = true 293 - rw.status = statusCode 294 - rw.ResponseWriter.WriteHeader(statusCode) 295 - } 296 - 297 - func (rw *responseTracker) Write(b []byte) (int, error) { 298 - if !rw.wroteHeader { 299 - rw.WriteHeader(http.StatusOK) 300 - } 301 - return rw.ResponseWriter.Write(b) 302 97 } 303 98 304 99 // Proxy is a forward proxy that substitutes its own certificate
+1 -1
src/mail.go
··· 1 - package main 1 + package liquidproxy 2 2 3 3 import ( 4 4 "bufio"
+2 -2
src/main.go src/core.go
··· 1 - package main 1 + package liquidproxy 2 2 3 3 import ( 4 4 "crypto/rsa" ··· 63 63 excludedMutex sync.RWMutex 64 64 ) 65 65 66 - func main() { 66 + func Run() { 67 67 // Read flags from flags.txt if it exists 68 68 if data, err := os.ReadFile("flags.txt"); err == nil { 69 69 flags := strings.Fields(string(data))
+1 -1
src/net.go
··· 1 - package main 1 + package liquidproxy 2 2 3 3 import ( 4 4 "bytes"
+1 -1
src/tls.go
··· 1 - package main 1 + package liquidproxy 2 2 3 3 import ( 4 4 "crypto/rand"
+2 -2
src/webui.go
··· 1 - package main 1 + package liquidproxy 2 2 3 3 import ( 4 4 "bufio" ··· 17 17 mimetype = "text/html" 18 18 code = 200 19 19 20 - if r.URL.Path == "/cert.pem" { 20 + if r.URL.Path == "/cert.pem" || r.URL.Path == "/cert.cer" { 21 21 file = certFile 22 22 mimetype = "application/octet-stream" 23 23 }