Skip to content

Commit 0255123

Browse files
therealalephclaude
andcommitted
fix: split YouTube domains in youtube_via_relay (#275)
Pre-#275, youtube_via_relay=true routed every YouTube-related host through Apps Script — including ytimg.com (thumbnails) and any googlevideo.com chunk request the player issued. Two problems: 1. ytimg.com via Apps Script is wasted quota — image CDN, no Restricted Mode logic to bypass. 2. googlevideo.com wasn't even in SNI_REWRITE_SUFFIXES, so video chunks hit the relay regardless of the flag. A single chunk timeout aborted the whole video on Firefox; long videos risked the Apps Script 6-min execution cap mid-playback. Fix: split YouTube into "API/HTML hosts" (where Restricted Mode lives, gated by the flag) and "asset CDNs" (always direct). The new YOUTUBE_RELAY_HOSTS list is youtube.com, youtu.be, youtube-nocookie.com, youtubei.googleapis.com — those go through relay when the flag is on. ytimg.com, googlevideo.com (added), ggpht.com all stay on SNI rewrite. The matches_sni_rewrite logic was also restructured: the carve-out now runs FIRST before the SNI suffix match, so the broad googleapis.com entry can't override the narrower youtubei.googleapis.com decision. Reported with detailed analysis by @amirabbas117. Will ship in v1.7.4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e7326ee commit 0255123

1 file changed

Lines changed: 111 additions & 25 deletions

File tree

src/proxy_server.rs

Lines changed: 111 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ const SNI_REWRITE_SUFFIXES: &[&str] = &[
4747
"youtu.be",
4848
"youtube-nocookie.com",
4949
"ytimg.com",
50+
// YouTube video CDN. Issue #275: a user reported that with
51+
// `youtube_via_relay = true`, every video chunk was traversing the
52+
// Apps Script relay and a single chunk timeout aborted playback —
53+
// because `googlevideo.com` was not on this list, all chunks
54+
// fell through to the relay path. Adding it here keeps video
55+
// bytes on the direct GFE tunnel even when the relay flag is on
56+
// (the YOUTUBE_RELAY_HOSTS carve-out below excludes only the API
57+
// / HTML surfaces, not the CDN). The chunks are unauthenticated
58+
// bytes anyway — there's no Restricted Mode logic on the CDN, so
59+
// routing them through Apps Script gains nothing and costs the
60+
// 6-minute Apps Script execution cap on long videos.
61+
"googlevideo.com",
5062
// Google Video Transport CDN — YouTube video chunks, Chrome
5163
// auto-updates, Google Play Store downloads. The single biggest
5264
// gap vs the upstream Python port: without these in the list
@@ -72,27 +84,62 @@ const SNI_REWRITE_SUFFIXES: &[&str] = &[
7284
"blogger.com",
7385
];
7486

75-
/// YouTube-family suffixes. Extracted so `youtube_via_relay` config can
76-
/// pull them out of the SNI-rewrite dispatch at runtime.
77-
const YOUTUBE_SNI_SUFFIXES: &[&str] = &[
87+
/// YouTube hosts that should be routed through the Apps Script relay
88+
/// when `youtube_via_relay` is enabled — the API + HTML surfaces where
89+
/// Restricted Mode is actually enforced (via the SNI=www.google.com
90+
/// edge looking at the request). Issue #102 / #275.
91+
///
92+
/// Deliberately narrower than the YouTube section of
93+
/// `SNI_REWRITE_SUFFIXES`:
94+
/// - `youtube.com` / `youtu.be` / `youtube-nocookie.com`: HTML pages
95+
/// and player frames. These trigger Restricted Mode if served via
96+
/// the SNI rewrite, so when the flag is on we relay them.
97+
/// - `youtubei.googleapis.com`: the YouTube data API the player
98+
/// queries for video metadata + manifest. Restricted Mode also
99+
/// gates video availability here. Without this entry, the JSON
100+
/// RPC layer would still hit the SNI-rewrite tunnel via the
101+
/// broader `googleapis.com` suffix — the user-visible symptom of
102+
/// that miss is "youtube_via_relay flips on but Restricted Mode
103+
/// stays sticky on some videos."
104+
///
105+
/// **NOT** in this list (intentional, was a regression in #275):
106+
/// - `ytimg.com`: thumbnails. No Restricted Mode logic on a static
107+
/// image CDN; routing through Apps Script makes thumbnails slow
108+
/// for zero gain.
109+
/// - `googlevideo.com`: video chunk CDN. Routing through Apps Script
110+
/// means every chunk eats Apps Script quota *and* risks the 6-min
111+
/// execution cap aborting long videos mid-playback.
112+
/// - `ggpht.com`: channel/profile images, same reasoning as ytimg.
113+
const YOUTUBE_RELAY_HOSTS: &[&str] = &[
78114
"youtube.com",
79115
"youtu.be",
80116
"youtube-nocookie.com",
81-
"ytimg.com",
117+
"youtubei.googleapis.com",
82118
];
83119

84120
fn matches_sni_rewrite(host: &str, youtube_via_relay: bool) -> bool {
85121
let h = host.to_ascii_lowercase();
86122
let h = h.trim_end_matches('.');
123+
124+
// YouTube relay carve-out runs FIRST so it wins over the broad
125+
// `googleapis.com` suffix that would otherwise pull
126+
// `youtubei.googleapis.com` into the SNI-rewrite path. The earlier
127+
// implementation iterated SNI_REWRITE_SUFFIXES with a filter, which
128+
// works for sibling entries (e.g. `youtube.com` in both lists) but
129+
// not for nested ones (`youtubei.googleapis.com` matches the broad
130+
// `googleapis.com` even when its specific entry is filtered out).
131+
// The short-circuit here is unconditional — we don't need to check
132+
// SNI rewrite once we've decided this host goes to the relay.
133+
if youtube_via_relay {
134+
for s in YOUTUBE_RELAY_HOSTS {
135+
if h == *s || h.ends_with(&format!(".{}", s)) {
136+
return false;
137+
}
138+
}
139+
}
140+
87141
SNI_REWRITE_SUFFIXES
88142
.iter()
89-
.filter(|s| {
90-
// If the user opted into youtube_via_relay, skip YouTube
91-
// suffixes so they fall through to the Apps Script relay
92-
// path. See config.rs `youtube_via_relay` docs for the
93-
// trade-off. Issue #102.
94-
!(youtube_via_relay && YOUTUBE_SNI_SUFFIXES.contains(s))
95-
})
96143
.any(|s| h == *s || h.ends_with(&format!(".{}", s)))
97144
}
98145

@@ -2587,36 +2634,75 @@ mod tests {
25872634

25882635
#[test]
25892636
fn youtube_via_relay_routes_youtube_through_relay_path() {
2590-
// Issue #102. When youtube_via_relay=true, YouTube suffixes
2591-
// must NOT match the SNI-rewrite path, so traffic falls
2592-
// through to Apps Script relay. Other Google suffixes are
2593-
// unaffected.
2637+
// Issue #102 + #275. When youtube_via_relay=true:
2638+
// - YouTube API + HTML hosts (where Restricted Mode lives)
2639+
// opt out of SNI rewrite so they go through the relay.
2640+
// - YouTube image / video / channel-asset CDNs STAY on SNI
2641+
// rewrite — Restricted Mode isn't enforced on those, and
2642+
// routing video chunks through Apps Script burns quota
2643+
// and risks the 6-min execution cap. Pre-#275 ytimg.com
2644+
// was incorrectly carved out alongside the API surfaces.
2645+
// - Non-YouTube Google suffixes are unaffected by the flag.
25942646
let hosts = std::collections::HashMap::new();
25952647

2596-
// Default behaviour: everything in the pool rewrites.
2648+
// Default behaviour (flag off): everything in the SNI pool
2649+
// rewrites including all YouTube assets.
2650+
assert!(should_use_sni_rewrite(&hosts, "www.youtube.com", 443, false));
2651+
assert!(should_use_sni_rewrite(&hosts, "i.ytimg.com", 443, false));
2652+
assert!(should_use_sni_rewrite(&hosts, "youtu.be", 443, false));
2653+
assert!(should_use_sni_rewrite(&hosts, "www.google.com", 443, false));
25972654
assert!(should_use_sni_rewrite(
25982655
&hosts,
2599-
"www.youtube.com",
2656+
"rr1---sn-abc.googlevideo.com",
2657+
443,
2658+
false
2659+
));
2660+
assert!(should_use_sni_rewrite(
2661+
&hosts,
2662+
"youtubei.googleapis.com",
26002663
443,
26012664
false
26022665
));
2603-
assert!(should_use_sni_rewrite(&hosts, "i.ytimg.com", 443, false));
2604-
assert!(should_use_sni_rewrite(&hosts, "youtu.be", 443, false));
2605-
assert!(should_use_sni_rewrite(&hosts, "www.google.com", 443, false));
26062666

2607-
// With the toggle on: YouTube opts out, Google stays.
2667+
// Flag on: only the API + HTML hosts opt out.
2668+
assert!(!should_use_sni_rewrite(&hosts, "www.youtube.com", 443, true));
2669+
assert!(!should_use_sni_rewrite(&hosts, "youtu.be", 443, true));
26082670
assert!(!should_use_sni_rewrite(
26092671
&hosts,
2610-
"www.youtube.com",
2672+
"www.youtube-nocookie.com",
26112673
443,
26122674
true
26132675
));
2614-
assert!(!should_use_sni_rewrite(&hosts, "i.ytimg.com", 443, true));
2615-
assert!(!should_use_sni_rewrite(&hosts, "youtu.be", 443, true));
2676+
assert!(!should_use_sni_rewrite(
2677+
&hosts,
2678+
"youtubei.googleapis.com",
2679+
443,
2680+
true
2681+
));
2682+
2683+
// Flag on: video / image / channel-asset CDNs STAY on SNI
2684+
// rewrite. The pre-#275 implementation broke playback by
2685+
// routing googlevideo.com through Apps Script (it wasn't even
2686+
// in the SNI list before #275, so it always went via relay)
2687+
// and routed ytimg.com through the relay too.
2688+
assert!(should_use_sni_rewrite(&hosts, "i.ytimg.com", 443, true));
2689+
assert!(should_use_sni_rewrite(
2690+
&hosts,
2691+
"rr1---sn-abc.googlevideo.com",
2692+
443,
2693+
true
2694+
));
2695+
assert!(should_use_sni_rewrite(&hosts, "yt3.ggpht.com", 443, true));
2696+
2697+
// Flag on: non-YouTube Google suffixes are unaffected. Note
2698+
// youtubei.googleapis.com (above) is the *carve-out* — the
2699+
// broader googleapis.com suffix is NOT carved out, so e.g.
2700+
// Drive / Calendar / etc. continue to SNI-rewrite.
26162701
assert!(should_use_sni_rewrite(&hosts, "www.google.com", 443, true));
2702+
assert!(should_use_sni_rewrite(&hosts, "fonts.gstatic.com", 443, true));
26172703
assert!(should_use_sni_rewrite(
26182704
&hosts,
2619-
"fonts.gstatic.com",
2705+
"drive.googleapis.com",
26202706
443,
26212707
true
26222708
));

0 commit comments

Comments
 (0)