Skip to content

Commit 3f014b0

Browse files
therealalephclaude
andcommitted
v1.6.2: fix "every download capped at 256 KB" (fix #162)
In `relay_parallel_range`, when a chunk failed validation (`extract_exact_range_body` returned Err) OR the stitched body length didn't match the advertised total, the fallback path called `rewrite_206_to_200(&first)` — which converted the 256 KiB probe response into HTTP 200 + Content-Length=262144 and returned that as if it were the full file. Browsers saw a complete-looking 200 and treated the download as finished at 256 KB. Common triggers for the chunk-validation failure (per the user reports): - Apps Script's UrlFetchApp stripping `Content-Range` from chunk responses while preserving it on the probe - Origin returning 200-OK on follow-up Range requests (some servers flatten ranges after the first one) - Mismatched `total` field across chunks for paths behind a varying cache layer The correct fallback is a single GET without any Range header — Apps Script fetches the whole URL (up to its 50 MiB cap) and returns a normal 200 with the complete body. Slower than parallel for large files but produces a correct response, which is the minimum bar. Two independent reports (Ehsan in #162, Recruit1992 confirming). 98 lib tests still pass; existing `validate_probe_range_rejects_*` and `extract_exact_range_body_*` tests already cover the validation side, the fallback path is observed integration-testing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 14e7dfc commit 3f014b0

5 files changed

Lines changed: 31 additions & 12 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mhrv-rs"
3-
version = "1.6.1"
3+
version = "1.6.2"
44
edition = "2021"
55
description = "Rust port of MasterHttpRelayVPN -- DPI bypass via Google Apps Script relay with domain fronting"
66
license = "MIT"

android/app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ android {
1414
applicationId = "com.therealaleph.mhrv"
1515
minSdk = 24 // Android 7.0 — covers 99%+ of live devices.
1616
targetSdk = 34
17-
versionCode = 140
18-
versionName = "1.6.1"
17+
versionCode = 141
18+
versionName = "1.6.2"
1919

2020
// Ship all four mainstream Android ABIs:
2121
// - arm64-v8a — 95%+ of real-world Android phones since 2019

docs/changelog/v1.6.2.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<!-- see docs/changelog/v1.1.0.md for the file format: Persian, then `---`, then English. -->
2+
• رفع باگ "همهٔ دانلودها روی ۲۵۶ کیلوبایت قطع میشن" ([#162](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/162)): در relay range-parallel، اگه validation هر chunk رد می‌شد (مثلاً Apps Script هدر `Content-Range` رو حذف می‌کرد، یا origin روی chunkهای بعدی به جای 206 یه 200 برمی‌گردوند)، fallback اشتباهی پاسخ probe (یعنی فقط ۲۵۶ کیلوبایت اول) رو به‌عنوان فایل کامل برمی‌گردوند. مرورگر `HTTP 200` با `Content-Length=262144` می‌دید و دانلود رو "کامل" تلقی می‌کرد. حالا fallback یک GET تک‌مرحله‌ای جدید بدون Range هدر می‌فرسته که Apps Script کل URL رو fetch کنه (تا سقف ۵۰ مگ). برای فایل‌های بزرگ‌تر کندتره از مسیر parallel، ولی پاسخ کامل می‌ده — که اون چیزی هست که اهمیت داره. ۲ کاربر مستقل این رو ریپورت کردن (Ehsan، Recruit1992)
3+
---
4+
• Fix "every download capped at 256 KB" bug ([#162](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/162)): in range-parallel relay, when any chunk failed validation (e.g. Apps Script stripping the `Content-Range` header on follow-up chunks, or origin returning 200-instead-of-206 on later chunks), the fallback path silently returned the probe response (the first 256 KiB) as if it were the full file. Browsers saw `HTTP 200` with `Content-Length=262144` and treated the download as complete. The fallback now does a fresh single GET without the Range header, letting Apps Script fetch the full URL (up to its 50 MiB cap). Slower than the parallel path for large files, but produces a complete response — which is what matters. Two independent users (Ehsan, Recruit1992) reported this; closed-loop with both

src/domain_fronter.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -765,24 +765,39 @@ impl DomainFronter {
765765
match chunk {
766766
Ok(chunk) => full.extend_from_slice(&chunk),
767767
Err(reason) => {
768+
// Issue #162: silently rewriting the probe to a 200
769+
// here truncates the response to whatever the probe
770+
// saw (typically 256 KiB — the chunk size). Browsers
771+
// see HTTP 200 + Content-Length=262144 and treat
772+
// the download as complete; users reported "every
773+
// file capped at 256 KB" because every download
774+
// that hit this failure path landed there. Common
775+
// triggers: Apps Script stripping Content-Range,
776+
// origin returning 200-instead-of-206 on later
777+
// chunks, total mismatch across chunks. Correct
778+
// recovery is a fresh single GET — Apps Script
779+
// fetches the full URL up to its 50 MiB cap. Slow
780+
// for big files vs. the parallel path but produces
781+
// a complete response, which is what matters.
768782
tracing::warn!(
769-
"range-parallel: invalid chunk {}-{} for {} ({}); falling back to probe response",
770-
start,
771-
end,
772-
url,
773-
reason,
783+
"range-parallel: invalid chunk {}-{} for {} ({}); falling back to single GET",
784+
start, end, url, reason,
774785
);
775-
return rewrite_206_to_200(&first);
786+
return self.relay(method, url, headers, body).await;
776787
}
777788
}
778789
}
779790

780791
if (full.len() as u64) != total {
792+
// Same fallback rationale as the chunk-validation case
793+
// above: returning the probe truncates to 256 KiB. Single
794+
// GET is the only way to give the user a complete file
795+
// when the parallel stitch can't be trusted.
781796
tracing::warn!(
782-
"range-parallel: stitched {}/{} bytes for {}; falling back to probe response",
797+
"range-parallel: stitched {}/{} bytes for {}; falling back to single GET",
783798
full.len(), total, url,
784799
);
785-
return rewrite_206_to_200(&first);
800+
return self.relay(method, url, headers, body).await;
786801
}
787802

788803
// Build a 200 OK with Content-Length = full body length. Drop

0 commit comments

Comments
 (0)