Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions android/app/src/main/java/com/therealaleph/mhrv/ConfigStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,31 @@ data class MhrvConfig(
*/
val youtubeViaRelay: Boolean = false,

/**
* Path-pinned relay routing (Rust `relay_url_patterns`, upstream
* commit b3b9220). Each entry is a `host/path-prefix` (no scheme,
* lowercase) — paths matching go through the Apps Script relay,
* non-matching paths on the same host fall through to a direct
* SNI-rewrite HTTP forward (saving Apps Script quota).
*
* The Rust side prepends the default `youtube.com/youtubei/`
* pattern at startup, with two suppression gates:
* - `youtubeViaRelay = true` (full YT through relay → filter is
* redundant)
* - exit-node "full" mode (every URL must route through the
* second-hop exit node → filter would bypass it)
* Plus, when either gate is active, user-supplied patterns whose
* host overlaps `YOUTUBE_RELAY_HOSTS` (youtube.com, youtu.be,
* youtube-nocookie.com, youtubei.googleapis.com) are dropped at
* startup with a warning, since they would partially defeat the
* full-relay contract.
*
* This Android-side field is for *additional* user entries only —
* round-tripped through config.json so a hand-edited extension
* survives a save. No UI editor (power-user knob).
*/
val relayUrlPatterns: List<String> = emptyList(),

/** UI language toggle. Non-Rust; honoured only by the Android wrapper. */
val uiLang: UiLang = UiLang.AUTO,
) {
Expand Down Expand Up @@ -240,6 +265,17 @@ data class MhrvConfig(
put("tunnel_doh", tunnelDoh)
put("block_doh", blockDoh)
if (youtubeViaRelay) put("youtube_via_relay", true)
// Trim/drop-empty/dedupe before serializing — same pattern
// as bypass_doh_hosts. Skip the key entirely when the user
// hasn't added any extras so we don't leak an empty array
// into otherwise-clean configs.
val cleanRelayUrlPatterns = relayUrlPatterns
.map { it.trim() }
.filter { it.isNotEmpty() }
.distinct()
if (cleanRelayUrlPatterns.isNotEmpty()) {
put("relay_url_patterns", JSONArray().apply { cleanRelayUrlPatterns.forEach { put(it) } })
}
// Trim/drop-empty/dedupe before serializing — symmetric with the
// read-side normalization in loadFromJson(), so a user typing
// " doh.foo " or accidentally adding a duplicate doesn't end up
Expand Down Expand Up @@ -356,6 +392,13 @@ object ConfigStore {
if (cleanBypassDohHosts.isNotEmpty()) {
obj.put("bypass_doh_hosts", JSONArray().apply { cleanBypassDohHosts.forEach { put(it) } })
}
val cleanRelayUrlPatterns = cfg.relayUrlPatterns
.map { it.trim() }
.filter { it.isNotEmpty() }
.distinct()
if (cleanRelayUrlPatterns.isNotEmpty()) {
obj.put("relay_url_patterns", JSONArray().apply { cleanRelayUrlPatterns.forEach { put(it) } })
}

// Compress with DEFLATE then base64.
val jsonBytes = obj.toString().toByteArray(Charsets.UTF_8)
Expand Down Expand Up @@ -459,6 +502,9 @@ object ConfigStore {
bypassDohHosts = obj.optJSONArray("bypass_doh_hosts")?.let { arr ->
buildList { for (i in 0 until arr.length()) add(arr.optString(i)) }
}?.filter { it.isNotBlank() }.orEmpty(),
relayUrlPatterns = obj.optJSONArray("relay_url_patterns")?.let { arr ->
buildList { for (i in 0 until arr.length()) add(arr.optString(i)) }
}?.filter { it.isNotBlank() }.orEmpty(),
connectionMode = when (obj.optString("connection_mode", "vpn_tun")) {
"proxy_only" -> ConnectionMode.PROXY_ONLY
else -> ConnectionMode.VPN_TUN
Expand Down
17 changes: 17 additions & 0 deletions src/bin/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ struct FormState {
google_ip_validation: bool,
normalize_x_graphql: bool,
youtube_via_relay: bool,
/// Round-tripped from config.json. No UI control yet — power-user
/// edit. See config.rs `relay_url_patterns` (b3b9220).
relay_url_patterns: Vec<String>,
passthrough_hosts: Vec<String>,
/// Round-tripped from config.json so the UI's save path doesn't
/// drop the user's setting. Not currently exposed as a UI control;
Expand Down Expand Up @@ -385,6 +388,7 @@ fn load_form() -> (FormState, Option<String>) {
scan_batch_size: c.scan_batch_size,
normalize_x_graphql: c.normalize_x_graphql,
youtube_via_relay: c.youtube_via_relay,
relay_url_patterns: c.relay_url_patterns.clone(),
passthrough_hosts: c.passthrough_hosts.clone(),
block_quic: c.block_quic,
disable_padding: c.disable_padding,
Expand Down Expand Up @@ -424,6 +428,7 @@ fn load_form() -> (FormState, Option<String>) {
scan_batch_size: 500,
normalize_x_graphql: false,
youtube_via_relay: false,
relay_url_patterns: Vec::new(),
passthrough_hosts: Vec::new(),
block_quic: true,
disable_padding: false,
Expand Down Expand Up @@ -580,6 +585,11 @@ impl FormState {
// config-only flag for now. Passed through from the loaded
// config if set, otherwise defaults to false.
youtube_via_relay: self.youtube_via_relay,
// relay_url_patterns is config-only too (b3b9220). No UI
// editor yet — the default `youtube.com/youtubei/` ships
// automatically; round-trip preserves any extras the user
// added by hand.
relay_url_patterns: self.relay_url_patterns.clone(),
// Similarly config-only for now; round-trips through the
// file so the UI doesn't drop the user's entries on save.
passthrough_hosts: self.passthrough_hosts.clone(),
Expand Down Expand Up @@ -666,6 +676,12 @@ struct ConfigWire<'a> {
normalize_x_graphql: bool,
#[serde(skip_serializing_if = "is_false")]
youtube_via_relay: bool,
/// Path-prefix relay routing (b3b9220). Default is empty — the
/// built-in `youtube.com/youtubei/` is added at proxy startup, not
/// written into config.json — so configs stay clean unless the user
/// added their own extras.
#[serde(skip_serializing_if = "Vec::is_empty")]
relay_url_patterns: &'a Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
passthrough_hosts: &'a Vec<String>,
// IP-scan knobs. These used to be missing from the wire struct, so
Expand Down Expand Up @@ -773,6 +789,7 @@ impl<'a> From<&'a Config> for ConfigWire<'a> {
.map(|v| v.iter().map(String::as_str).collect()),
normalize_x_graphql: c.normalize_x_graphql,
youtube_via_relay: c.youtube_via_relay,
relay_url_patterns: &c.relay_url_patterns,
passthrough_hosts: &c.passthrough_hosts,
fetch_ips_from_api: c.fetch_ips_from_api,
max_ips_to_scan: c.max_ips_to_scan,
Expand Down
Loading
Loading