Skip to content

feat: support schemeless proxied URLs with a configurable default scheme#141

Open
lemuelbarango wants to merge 7 commits into
everywall:mainfrom
lemuelbarango:feat/default-scheme
Open

feat: support schemeless proxied URLs with a configurable default scheme#141
lemuelbarango wants to merge 7 commits into
everywall:mainfrom
lemuelbarango:feat/default-scheme

Conversation

@lemuelbarango

@lemuelbarango lemuelbarango commented Jun 17, 2026

Copy link
Copy Markdown

Summary

Requesting a URL through the proxy without an http:// or https:// prefix (e.g. http://localhost:8080/example.com/page) currently fails with:

unsupported protocol scheme ""

This PR makes the proxy prepend a configured default scheme to schemeless URLs so they work end-to-end.

Behavior

  • New DEFAULT_SCHEME env var and --default-scheme / -s CLI flag. Default is https.
  • Only http and https are accepted; anything else is rejected at startup in cmd/main.go with a clear error (no log.Fatal from init(), so utility flows like --merge-rulesets aren't affected by a stale env var).
  • URLs that already have a scheme:// prefix (including http://, https://, ftp://, chrome-extension://, etc.) are passed through unchanged.
  • The existing Referer-based relative-path reconstruction still takes precedence — only when the referer can't reconstruct a proxied URL does the default-scheme fallback kick in.
  • Normalization runs once per request at the handler boundary (extractUrl fallback for ProxySite, Api, and Raw), not inside fetchSite.

Implementation notes

ensureScheme uses a simple ://-substring check rather than url.Parse, because Go's parser is RFC-3986 lenient enough to treat example.com:8443/page as having scheme example.com — exactly the input we want to normalize. The trade-off is that non-network URI schemes without :// (e.g. mailto:foo@bar.com) also get the default scheme prepended; they still fail, just at DNS rather than at the stdlib protocol check. This is documented in the test cases.

Test plan

handlers/default_scheme_test.go covers:

  • SetDefaultScheme accepts http/https (case-insensitive, trimmed) and rejects everything else.
  • ensureScheme unit tests across schemed URLs, bare hosts, host/path, host:port/path, single-segment hosts (localhost/api), relative paths (/images/foo.jpg), stray ://, non-network schemes, and ftp://.
  • extractUrl integration via Fiber: schemeless URL with no referer gets the default scheme; explicit http:// and https:// URLs pass through; relative paths reconstruct from a proxied referer; configured http scheme is honored.
  • Stdlib bug repro: pins the exact unsupported protocol scheme "" error so we'd notice if Go changes the wording.
  • End-to-end via the Raw handler against an httptest upstream, exercising the new boundary normalization.

Run:

go test ./...
go test -race ./handlers/...

Both green.

Requesting a URL without an http(s):// prefix (e.g. /example.com/page)
previously failed with 'unsupported protocol scheme ""'. The proxy now
prepends a configured default scheme to such requests so they Just Work.

The default is 'https' and is configurable via the DEFAULT_SCHEME env
var or the --default-scheme / -s CLI flag. Only 'http' and 'https' are
accepted; any other value causes a startup error.

Behavior for URLs that already include a scheme is unchanged, and the
existing referer-based relative-path reconstruction still takes
precedence when the referer points at a proxied URL.
…nit log.Fatal

- ensureScheme now uses a '://' substring check instead of magic-length and
  dot/colon heuristics, so single-segment hosts (localhost/api) and
  schemeless host:port URLs (example.com:8443/x) now work.
- Strip a stray leading '://' so '://example.com' no longer produces
  'https://://example.com'.
- Move normalization out of fetchSite and into the handler boundaries
  (extractUrl, api, raw) — single normalization point per entry.
- Drop the init() block that called log.Fatal on invalid DEFAULT_SCHEME.
  Validation now happens once in cmd/main.go where it's recoverable, so
  utility commands like --merge-rulesets aren't killed by an unrelated
  env var.
@lemuelbarango lemuelbarango changed the title feat: prepend a configurable default scheme to schemeless proxied URLs feat: support schemeless proxied URLs with a configurable default scheme Jun 17, 2026
The applyRules function was defined but never called from any code path,
making the entire ruleset infrastructure (regexRules, injections) dead code.
The only ruleset fields that took effect were Headers, URLMods, and the
incidental side effects of rewriteHtml (which renames `src="/..."` to
`script="..."` on relative-URL script tags).

Invoke applyRules in fetchSite, BEFORE rewriteHtml. Order matters:
- regexRules patterns like `<script[^>]*src="..."` need to see original src
  attributes (rewriteHtml renames them and breaks subsequent matches).
- injections add content to <head>, position-independent — but easier to
  follow if all ruleset work happens before URL munging.
Three tests in handlers/apply_rules_test.go:

- TestApplyRulesUnit — applyRules itself accepts regexRules + injections and
  applies both. Always passed; documents the surface area.
- TestFetchSiteInvokesApplyRules — regression test for the dead-code bug.
  Spins up an httptest.NewServer, configures a rule with regexRules and
  injections, calls fetchSite, asserts both effects landed. Fails without
  the applyRules invocation in fetchSite.
- TestApplyRulesOrderingBeforeRewriteHtml — codifies the ordering: applyRules
  must run BEFORE rewriteHtml because rewriteHtml renames src= attributes on
  relative-URL script tags, which would otherwise prevent src-based regex
  patterns from matching. Fails if the call order is swapped.

Verified: with the fix in proxy.go reverted, the second and third tests
fail with clear error messages pointing at the ruleset effects that didn't
land.
@mms-gianni

Copy link
Copy Markdown
Contributor

I'll need a moment to test this. But looks promising.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants