feat(webkit): add Cloudflare Turnstile CAPTCHA workaround
Implements temporary CORS allowlist for challenges.cloudflare.com to work around WebKit's incomplete COEP: credentialless support. This prevents Turnstile widgets from failing due to CORS violations. Changes: - Added EnableTurnstileWorkaround config flag (default: true) - WebView applies CORS allowlist for challenges.cloudflare.com when enabled - Added resource logging to track CORP/COEP/COOP headers for debugging - Disabled console.log() spam to stdout (DevTools console still works) Technical details: - Uses WebKit's SetCorsAllowlist() API to bypass same-origin policy - Logs Turnstile resource loads with status codes and headers - Helper functions: isTurnstileURL(), headerValue() - Temporary until WebKitGTK properly implements COEP: credentialless This allows Cloudflare-protected sites to function properly while maintaining the option to disable the workaround via config if needed.
- commit
- de91544e335352bb7dc661ecf58d07cb40610e6a
- author
- bnema · · 2025-11-17 17:22 UTC
- committer
- bnema · · 2025-11-17 22:36 UTC
- parent
- 202ed5a
- signature
- unsigned
3 files · +131 -24
M internal/app/browser/webview.go +14 -13
| @@ -43,19 +43,20 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) { | |||
| 43 | 43 | ||
| 44 | 44 | // Build WebKit configuration with data directories for persistence | |
| 45 | 45 | cfg := &webkit.Config{ | |
| 46 | - | UserAgent: buildUserAgent(app.config), | |
| 47 | - | EnableJavaScript: true, | |
| 48 | - | EnableWebGL: true, | |
| 49 | - | EnableMediaStream: true, | |
| 50 | - | HardwareAcceleration: true, | |
| 51 | - | DefaultFontSize: app.config.Appearance.DefaultFontSize, | |
| 52 | - | MinimumFontSize: 8, | |
| 53 | - | EnablePageCache: true, // Instant back/forward navigation (bfcache) | |
| 54 | - | EnableSmoothScrolling: true, // Smooth scrolling animations | |
| 55 | - | DataDir: webkitData, | |
| 56 | - | CacheDir: webkitCache, | |
| 57 | - | AppearanceConfigJSON: app.buildAppearanceConfigJSON(), | |
| 58 | - | CreateWindow: true, // Default to creating a window for standalone WebViews | |
| 46 | + | UserAgent: buildUserAgent(app.config), | |
| 47 | + | EnableJavaScript: true, | |
| 48 | + | EnableWebGL: true, | |
| 49 | + | EnableMediaStream: true, | |
| 50 | + | HardwareAcceleration: true, | |
| 51 | + | DefaultFontSize: app.config.Appearance.DefaultFontSize, | |
| 52 | + | MinimumFontSize: 8, | |
| 53 | + | EnablePageCache: true, // Instant back/forward navigation (bfcache) | |
| 54 | + | EnableSmoothScrolling: true, // Smooth scrolling animations | |
| 55 | + | DataDir: webkitData, | |
| 56 | + | CacheDir: webkitCache, | |
| 57 | + | AppearanceConfigJSON: app.buildAppearanceConfigJSON(), | |
| 58 | + | CreateWindow: true, // Default to creating a window for standalone WebViews | |
| 59 | + | EnableTurnstileWorkaround: true, | |
| 59 | 60 | } | |
| 60 | 61 | ||
| 61 | 62 | return cfg, nil | |
| @@ -43,19 +43,20 @@ func (app *BrowserApp) buildWebkitConfig() (*webkit.Config, error) { | |||||
| 43 | 43 | ||||
| 44 | // Build WebKit configuration with data directories for persistence | 44 | // Build WebKit configuration with data directories for persistence | ||
| 45 | cfg := &webkit.Config{ | 45 | cfg := &webkit.Config{ | ||
| 46 | - | UserAgent: buildUserAgent(app.config), | 46 | + | UserAgent: buildUserAgent(app.config), |
| 47 | - | EnableJavaScript: true, | 47 | + | EnableJavaScript: true, |
| 48 | - | EnableWebGL: true, | 48 | + | EnableWebGL: true, |
| 49 | - | EnableMediaStream: true, | 49 | + | EnableMediaStream: true, |
| 50 | - | HardwareAcceleration: true, | 50 | + | HardwareAcceleration: true, |
| 51 | - | DefaultFontSize: app.config.Appearance.DefaultFontSize, | 51 | + | DefaultFontSize: app.config.Appearance.DefaultFontSize, |
| 52 | - | MinimumFontSize: 8, | 52 | + | MinimumFontSize: 8, |
| 53 | - | EnablePageCache: true, // Instant back/forward navigation (bfcache) | 53 | + | EnablePageCache: true, // Instant back/forward navigation (bfcache) |
| 54 | - | EnableSmoothScrolling: true, // Smooth scrolling animations | 54 | + | EnableSmoothScrolling: true, // Smooth scrolling animations |
| 55 | - | DataDir: webkitData, | 55 | + | DataDir: webkitData, |
| 56 | - | CacheDir: webkitCache, | 56 | + | CacheDir: webkitCache, |
| 57 | - | AppearanceConfigJSON: app.buildAppearanceConfigJSON(), | 57 | + | AppearanceConfigJSON: app.buildAppearanceConfigJSON(), |
| 58 | - | CreateWindow: true, // Default to creating a window for standalone WebViews | 58 | + | CreateWindow: true, // Default to creating a window for standalone WebViews |
| 59 | + | EnableTurnstileWorkaround: true, | |||
| 59 | } | 60 | } | ||
| 60 | 61 | ||||
| 61 | return cfg, nil | 62 | return cfg, nil | ||
M pkg/webkit/types.go +16 -10
| @@ -65,20 +65,26 @@ type Config struct { | |||
| 65 | 65 | // CreateWindow controls whether to create a standalone GTK Window for this WebView | |
| 66 | 66 | // Set to false for WebViews that will be embedded in workspace panes | |
| 67 | 67 | CreateWindow bool | |
| 68 | + | ||
| 69 | + | // EnableTurnstileWorkaround temporarily disables CORS enforcement for the | |
| 70 | + | // Cloudflare Turnstile host to work around WebKit's credentialless COEP gap. | |
| 71 | + | // Remove once upstream WebKit properly supports COEP: credentialless. | |
| 72 | + | EnableTurnstileWorkaround bool | |
| 68 | 73 | } | |
| 69 | 74 | ||
| 70 | 75 | // GetDefaultConfig returns a Config with sensible defaults | |
| 71 | 76 | func GetDefaultConfig() *Config { | |
| 72 | 77 | return &Config{ | |
| 73 | - | UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", | |
| 74 | - | EnableJavaScript: true, | |
| 75 | - | EnableWebGL: true, | |
| 76 | - | EnableMediaStream: true, | |
| 77 | - | HardwareAcceleration: true, | |
| 78 | - | DefaultFontSize: 16, | |
| 79 | - | MinimumFontSize: 8, | |
| 80 | - | EnablePageCache: true, // Instant back/forward navigation | |
| 81 | - | EnableSmoothScrolling: true, // Better UX | |
| 82 | - | CreateWindow: true, // Default to standalone window | |
| 78 | + | UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15", // WebKitGTK default (same as Epiphany) | |
| 79 | + | EnableJavaScript: true, | |
| 80 | + | EnableWebGL: true, | |
| 81 | + | EnableMediaStream: true, | |
| 82 | + | HardwareAcceleration: true, | |
| 83 | + | DefaultFontSize: 16, | |
| 84 | + | MinimumFontSize: 8, | |
| 85 | + | EnablePageCache: true, // Instant back/forward navigation | |
| 86 | + | EnableSmoothScrolling: true, // Better UX | |
| 87 | + | CreateWindow: true, // Default to standalone window | |
| 88 | + | EnableTurnstileWorkaround: true, | |
| 83 | 89 | } | |
| 84 | 90 | } | |
| @@ -65,20 +65,26 @@ type Config struct { | |||||
| 65 | // CreateWindow controls whether to create a standalone GTK Window for this WebView | 65 | // CreateWindow controls whether to create a standalone GTK Window for this WebView | ||
| 66 | // Set to false for WebViews that will be embedded in workspace panes | 66 | // Set to false for WebViews that will be embedded in workspace panes | ||
| 67 | CreateWindow bool | 67 | CreateWindow bool | ||
| 68 | + | ||||
| 69 | + | // EnableTurnstileWorkaround temporarily disables CORS enforcement for the | |||
| 70 | + | // Cloudflare Turnstile host to work around WebKit's credentialless COEP gap. | |||
| 71 | + | // Remove once upstream WebKit properly supports COEP: credentialless. | |||
| 72 | + | EnableTurnstileWorkaround bool | |||
| 68 | } | 73 | } | ||
| 69 | 74 | ||||
| 70 | // GetDefaultConfig returns a Config with sensible defaults | 75 | // GetDefaultConfig returns a Config with sensible defaults | ||
| 71 | func GetDefaultConfig() *Config { | 76 | func GetDefaultConfig() *Config { | ||
| 72 | return &Config{ | 77 | return &Config{ | ||
| 73 | - | UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", | 78 | + | UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15", // WebKitGTK default (same as Epiphany) |
| 74 | - | EnableJavaScript: true, | 79 | + | EnableJavaScript: true, |
| 75 | - | EnableWebGL: true, | 80 | + | EnableWebGL: true, |
| 76 | - | EnableMediaStream: true, | 81 | + | EnableMediaStream: true, |
| 77 | - | HardwareAcceleration: true, | 82 | + | HardwareAcceleration: true, |
| 78 | - | DefaultFontSize: 16, | 83 | + | DefaultFontSize: 16, |
| 79 | - | MinimumFontSize: 8, | 84 | + | MinimumFontSize: 8, |
| 80 | - | EnablePageCache: true, // Instant back/forward navigation | 85 | + | EnablePageCache: true, // Instant back/forward navigation |
| 81 | - | EnableSmoothScrolling: true, // Better UX | 86 | + | EnableSmoothScrolling: true, // Better UX |
| 82 | - | CreateWindow: true, // Default to standalone window | 87 | + | CreateWindow: true, // Default to standalone window |
| 88 | + | EnableTurnstileWorkaround: true, | |||
| 83 | } | 89 | } | ||
| 84 | } | 90 | } | ||
M pkg/webkit/webview.go +101 -1
| @@ -4,7 +4,9 @@ import ( | |||
| 4 | 4 | "encoding/json" | |
| 5 | 5 | "fmt" | |
| 6 | 6 | "log" | |
| 7 | + | "net/url" | |
| 7 | 8 | "strconv" | |
| 9 | + | "strings" | |
| 8 | 10 | "sync" | |
| 9 | 11 | "sync/atomic" | |
| 10 | 12 | ||
| @@ -13,6 +15,8 @@ import ( | |||
| 13 | 15 | "github.com/diamondburned/gotk4/pkg/gtk/v4" | |
| 14 | 16 | ) | |
| 15 | 17 | ||
| 18 | + | const turnstileHost = "challenges.cloudflare.com" | |
| 19 | + | ||
| 16 | 20 | var ( | |
| 17 | 21 | viewIDCounter uint64 | |
| 18 | 22 | viewRegistry = make(map[uint64]*WebView) | |
| @@ -38,7 +42,7 @@ type WebView struct { | |||
| 38 | 42 | onFaviconChanged func([]byte) | |
| 39 | 43 | onFaviconURIChanged func(pageURI, faviconURI string) | |
| 40 | 44 | onZoomChanged func(float64) | |
| 41 | - | onLoadCommitted func(string) // Called when page load is committed (safe to apply zoom) | |
| 45 | + | onLoadCommitted func(string) // Called when page load is committed (safe to apply zoom) | |
| 42 | 46 | onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler | |
| 43 | 47 | onReadyToShow func() // WebKit ready-to-show signal handler | |
| 44 | 48 | onClose func() | |
| @@ -206,6 +210,11 @@ func (w *WebView) applyConfig() error { | |||
| 206 | 210 | // Enable smooth scrolling for better UX | |
| 207 | 211 | settings.SetEnableSmoothScrolling(w.config.EnableSmoothScrolling) | |
| 208 | 212 | ||
| 213 | + | // Disable console messages to stdout to prevent page script flooding | |
| 214 | + | // This stops malicious/buggy pages from spamming the terminal with console.log() | |
| 215 | + | // Inspector/DevTools console (F12) will still show all messages | |
| 216 | + | settings.SetEnableWriteConsoleMessagesToStdout(false) | |
| 217 | + | ||
| 209 | 218 | return nil | |
| 210 | 219 | } | |
| 211 | 220 | ||
| @@ -303,6 +312,12 @@ func (w *WebView) setupEventHandlers() { | |||
| 303 | 312 | ||
| 304 | 313 | // Setup navigation policy handler for middle-click and Ctrl+click interception | |
| 305 | 314 | w.setupNavigationPolicyHandler() | |
| 315 | + | ||
| 316 | + | // Log CORP/CORP headers for Cloudflare Turnstile resources to help diagnose COEP issues | |
| 317 | + | w.attachTurnstileResponseLogger() | |
| 318 | + | ||
| 319 | + | // Apply temporary Turnstile workaround until WebKit supports COEP: credentialless | |
| 320 | + | w.applyTurnstileCorsAllowlist() | |
| 306 | 321 | } | |
| 307 | 322 | ||
| 308 | 323 | // setupNavigationPolicyHandler sets up the decide-policy signal handler | |
| @@ -381,6 +396,91 @@ func (w *WebView) setupNavigationPolicyHandler() { | |||
| 381 | 396 | log.Printf("[webkit] Navigation policy handler attached to WebView ID %d", w.id) | |
| 382 | 397 | } | |
| 383 | 398 | ||
| 399 | + | func (w *WebView) applyTurnstileCorsAllowlist() { | |
| 400 | + | if w.view == nil || w.config == nil || !w.config.EnableTurnstileWorkaround { | |
| 401 | + | return | |
| 402 | + | } | |
| 403 | + | ||
| 404 | + | pattern := fmt.Sprintf("https://%s/*", turnstileHost) | |
| 405 | + | w.view.SetCorsAllowlist([]string{pattern}) | |
| 406 | + | log.Printf("[turnstile] Applied CORS allowlist for %s on WebView ID %d", turnstileHost, w.id) | |
| 407 | + | } | |
| 408 | + | ||
| 409 | + | func (w *WebView) attachTurnstileResponseLogger() { | |
| 410 | + | if w.view == nil { | |
| 411 | + | return | |
| 412 | + | } | |
| 413 | + | ||
| 414 | + | w.view.ConnectResourceLoadStarted(func(resource *webkit.WebResource, request *webkit.URIRequest) { | |
| 415 | + | if resource == nil || request == nil { | |
| 416 | + | return | |
| 417 | + | } | |
| 418 | + | ||
| 419 | + | targetURL := request.URI() | |
| 420 | + | if targetURL == "" { | |
| 421 | + | targetURL = resource.URI() | |
| 422 | + | } | |
| 423 | + | if !isTurnstileURL(targetURL) { | |
| 424 | + | return | |
| 425 | + | } | |
| 426 | + | ||
| 427 | + | method := request.HTTPMethod() | |
| 428 | + | if method == "" { | |
| 429 | + | method = "GET" | |
| 430 | + | } | |
| 431 | + | ||
| 432 | + | referer := "<none>" | |
| 433 | + | if reqHeaders := request.HTTPHeaders(); reqHeaders != nil { | |
| 434 | + | if ref := strings.TrimSpace(reqHeaders.One("Referer")); ref != "" { | |
| 435 | + | referer = ref | |
| 436 | + | } | |
| 437 | + | } | |
| 438 | + | ||
| 439 | + | resource.Connect("notify::response", func() { | |
| 440 | + | response := resource.Response() | |
| 441 | + | if response == nil { | |
| 442 | + | log.Printf("[turnstile] resource=%s method=%s status=<unknown> referer=%s corp=<missing> coep=<missing> coop=<missing>", targetURL, method, referer) | |
| 443 | + | return | |
| 444 | + | } | |
| 445 | + | ||
| 446 | + | headers := response.HTTPHeaders() | |
| 447 | + | corp := headerValue(headers, "Cross-Origin-Resource-Policy") | |
| 448 | + | coep := headerValue(headers, "Cross-Origin-Embedder-Policy") | |
| 449 | + | coop := headerValue(headers, "Cross-Origin-Opener-Policy") | |
| 450 | + | ||
| 451 | + | log.Printf("[turnstile] resource=%s method=%s status=%d referer=%s corp=%s coep=%s coop=%s", | |
| 452 | + | targetURL, method, response.StatusCode(), referer, corp, coep, coop) | |
| 453 | + | }) | |
| 454 | + | }) | |
| 455 | + | } | |
| 456 | + | ||
| 457 | + | type headerLookup interface { | |
| 458 | + | One(string) string | |
| 459 | + | } | |
| 460 | + | ||
| 461 | + | func headerValue(headers headerLookup, name string) string { | |
| 462 | + | if headers == nil { | |
| 463 | + | return "<missing>" | |
| 464 | + | } | |
| 465 | + | value := strings.TrimSpace(headers.One(name)) | |
| 466 | + | if value == "" { | |
| 467 | + | return "<missing>" | |
| 468 | + | } | |
| 469 | + | return value | |
| 470 | + | } | |
| 471 | + | ||
| 472 | + | func isTurnstileURL(raw string) bool { | |
| 473 | + | if raw == "" { | |
| 474 | + | return false | |
| 475 | + | } | |
| 476 | + | parsed, err := url.Parse(raw) | |
| 477 | + | if err != nil { | |
| 478 | + | return false | |
| 479 | + | } | |
| 480 | + | host := strings.ToLower(parsed.Hostname()) | |
| 481 | + | return host == turnstileHost | |
| 482 | + | } | |
| 483 | + | ||
| 384 | 484 | // LoadURL loads the given URL in the WebView | |
| 385 | 485 | func (w *WebView) LoadURL(url string) error { | |
| 386 | 486 | w.mu.RLock() | |
| @@ -4,7 +4,9 @@ import ( | |||||
| 4 | "encoding/json" | 4 | "encoding/json" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | "log" | 6 | "log" | ||
| 7 | + | "net/url" | |||
| 7 | "strconv" | 8 | "strconv" | ||
| 9 | + | "strings" | |||
| 8 | "sync" | 10 | "sync" | ||
| 9 | "sync/atomic" | 11 | "sync/atomic" | ||
| 10 | 12 | ||||
| @@ -13,6 +15,8 @@ import ( | |||||
| 13 | "github.com/diamondburned/gotk4/pkg/gtk/v4" | 15 | "github.com/diamondburned/gotk4/pkg/gtk/v4" | ||
| 14 | ) | 16 | ) | ||
| 15 | 17 | ||||
| 18 | + | const turnstileHost = "challenges.cloudflare.com" | |||
| 19 | + | ||||
| 16 | var ( | 20 | var ( | ||
| 17 | viewIDCounter uint64 | 21 | viewIDCounter uint64 | ||
| 18 | viewRegistry = make(map[uint64]*WebView) | 22 | viewRegistry = make(map[uint64]*WebView) | ||
| @@ -38,7 +42,7 @@ type WebView struct { | |||||
| 38 | onFaviconChanged func([]byte) | 42 | onFaviconChanged func([]byte) | ||
| 39 | onFaviconURIChanged func(pageURI, faviconURI string) | 43 | onFaviconURIChanged func(pageURI, faviconURI string) | ||
| 40 | onZoomChanged func(float64) | 44 | onZoomChanged func(float64) | ||
| 41 | - | onLoadCommitted func(string) // Called when page load is committed (safe to apply zoom) | 45 | + | onLoadCommitted func(string) // Called when page load is committed (safe to apply zoom) |
| 42 | onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler | 46 | onPopupCreate func(*webkit.NavigationAction) *WebView // New WebKit create signal handler | ||
| 43 | onReadyToShow func() // WebKit ready-to-show signal handler | 47 | onReadyToShow func() // WebKit ready-to-show signal handler | ||
| 44 | onClose func() | 48 | onClose func() | ||
| @@ -206,6 +210,11 @@ func (w *WebView) applyConfig() error { | |||||
| 206 | // Enable smooth scrolling for better UX | 210 | // Enable smooth scrolling for better UX | ||
| 207 | settings.SetEnableSmoothScrolling(w.config.EnableSmoothScrolling) | 211 | settings.SetEnableSmoothScrolling(w.config.EnableSmoothScrolling) | ||
| 208 | 212 | ||||
| 213 | + | // Disable console messages to stdout to prevent page script flooding | |||
| 214 | + | // This stops malicious/buggy pages from spamming the terminal with console.log() | |||
| 215 | + | // Inspector/DevTools console (F12) will still show all messages | |||
| 216 | + | settings.SetEnableWriteConsoleMessagesToStdout(false) | |||
| 217 | + | ||||
| 209 | return nil | 218 | return nil | ||
| 210 | } | 219 | } | ||
| 211 | 220 | ||||
| @@ -303,6 +312,12 @@ func (w *WebView) setupEventHandlers() { | |||||
| 303 | 312 | ||||
| 304 | // Setup navigation policy handler for middle-click and Ctrl+click interception | 313 | // Setup navigation policy handler for middle-click and Ctrl+click interception | ||
| 305 | w.setupNavigationPolicyHandler() | 314 | w.setupNavigationPolicyHandler() | ||
| 315 | + | ||||
| 316 | + | // Log CORP/CORP headers for Cloudflare Turnstile resources to help diagnose COEP issues | |||
| 317 | + | w.attachTurnstileResponseLogger() | |||
| 318 | + | ||||
| 319 | + | // Apply temporary Turnstile workaround until WebKit supports COEP: credentialless | |||
| 320 | + | w.applyTurnstileCorsAllowlist() | |||
| 306 | } | 321 | } | ||
| 307 | 322 | ||||
| 308 | // setupNavigationPolicyHandler sets up the decide-policy signal handler | 323 | // setupNavigationPolicyHandler sets up the decide-policy signal handler | ||
| @@ -381,6 +396,91 @@ func (w *WebView) setupNavigationPolicyHandler() { | |||||
| 381 | log.Printf("[webkit] Navigation policy handler attached to WebView ID %d", w.id) | 396 | log.Printf("[webkit] Navigation policy handler attached to WebView ID %d", w.id) | ||
| 382 | } | 397 | } | ||
| 383 | 398 | ||||
| 399 | + | func (w *WebView) applyTurnstileCorsAllowlist() { | |||
| 400 | + | if w.view == nil || w.config == nil || !w.config.EnableTurnstileWorkaround { | |||
| 401 | + | return | |||
| 402 | + | } | |||
| 403 | + | ||||
| 404 | + | pattern := fmt.Sprintf("https://%s/*", turnstileHost) | |||
| 405 | + | w.view.SetCorsAllowlist([]string{pattern}) | |||
| 406 | + | log.Printf("[turnstile] Applied CORS allowlist for %s on WebView ID %d", turnstileHost, w.id) | |||
| 407 | + | } | |||
| 408 | + | ||||
| 409 | + | func (w *WebView) attachTurnstileResponseLogger() { | |||
| 410 | + | if w.view == nil { | |||
| 411 | + | return | |||
| 412 | + | } | |||
| 413 | + | ||||
| 414 | + | w.view.ConnectResourceLoadStarted(func(resource *webkit.WebResource, request *webkit.URIRequest) { | |||
| 415 | + | if resource == nil || request == nil { | |||
| 416 | + | return | |||
| 417 | + | } | |||
| 418 | + | ||||
| 419 | + | targetURL := request.URI() | |||
| 420 | + | if targetURL == "" { | |||
| 421 | + | targetURL = resource.URI() | |||
| 422 | + | } | |||
| 423 | + | if !isTurnstileURL(targetURL) { | |||
| 424 | + | return | |||
| 425 | + | } | |||
| 426 | + | ||||
| 427 | + | method := request.HTTPMethod() | |||
| 428 | + | if method == "" { | |||
| 429 | + | method = "GET" | |||
| 430 | + | } | |||
| 431 | + | ||||
| 432 | + | referer := "<none>" | |||
| 433 | + | if reqHeaders := request.HTTPHeaders(); reqHeaders != nil { | |||
| 434 | + | if ref := strings.TrimSpace(reqHeaders.One("Referer")); ref != "" { | |||
| 435 | + | referer = ref | |||
| 436 | + | } | |||
| 437 | + | } | |||
| 438 | + | ||||
| 439 | + | resource.Connect("notify::response", func() { | |||
| 440 | + | response := resource.Response() | |||
| 441 | + | if response == nil { | |||
| 442 | + | log.Printf("[turnstile] resource=%s method=%s status=<unknown> referer=%s corp=<missing> coep=<missing> coop=<missing>", targetURL, method, referer) | |||
| 443 | + | return | |||
| 444 | + | } | |||
| 445 | + | ||||
| 446 | + | headers := response.HTTPHeaders() | |||
| 447 | + | corp := headerValue(headers, "Cross-Origin-Resource-Policy") | |||
| 448 | + | coep := headerValue(headers, "Cross-Origin-Embedder-Policy") | |||
| 449 | + | coop := headerValue(headers, "Cross-Origin-Opener-Policy") | |||
| 450 | + | ||||
| 451 | + | log.Printf("[turnstile] resource=%s method=%s status=%d referer=%s corp=%s coep=%s coop=%s", | |||
| 452 | + | targetURL, method, response.StatusCode(), referer, corp, coep, coop) | |||
| 453 | + | }) | |||
| 454 | + | }) | |||
| 455 | + | } | |||
| 456 | + | ||||
| 457 | + | type headerLookup interface { | |||
| 458 | + | One(string) string | |||
| 459 | + | } | |||
| 460 | + | ||||
| 461 | + | func headerValue(headers headerLookup, name string) string { | |||
| 462 | + | if headers == nil { | |||
| 463 | + | return "<missing>" | |||
| 464 | + | } | |||
| 465 | + | value := strings.TrimSpace(headers.One(name)) | |||
| 466 | + | if value == "" { | |||
| 467 | + | return "<missing>" | |||
| 468 | + | } | |||
| 469 | + | return value | |||
| 470 | + | } | |||
| 471 | + | ||||
| 472 | + | func isTurnstileURL(raw string) bool { | |||
| 473 | + | if raw == "" { | |||
| 474 | + | return false | |||
| 475 | + | } | |||
| 476 | + | parsed, err := url.Parse(raw) | |||
| 477 | + | if err != nil { | |||
| 478 | + | return false | |||
| 479 | + | } | |||
| 480 | + | host := strings.ToLower(parsed.Hostname()) | |||
| 481 | + | return host == turnstileHost | |||
| 482 | + | } | |||
| 483 | + | ||||
| 384 | // LoadURL loads the given URL in the WebView | 484 | // LoadURL loads the given URL in the WebView | ||
| 385 | func (w *WebView) LoadURL(url string) error { | 485 | func (w *WebView) LoadURL(url string) error { | ||
| 386 | w.mu.RLock() | 486 | w.mu.RLock() | ||