mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
refactor(app-proxy): remove the stable time freeze (#2856)
This commit is contained in:
@@ -71,7 +71,6 @@ jobs:
|
||||
BUILD_VERSION: ${{ needs.meta.outputs.build_version }}
|
||||
PUBLIC_ASSET_BASE_URL: ""
|
||||
BUNDLE_LOCAL_ASSETS: "true"
|
||||
FLUXER_APP_PROXY_TIME_FREEZE_ENABLED: "false"
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
@@ -155,7 +154,6 @@ jobs:
|
||||
BUILD_VERSION=${{ needs.meta.outputs.build_version }}
|
||||
SOURCE_SHA=${{ github.sha }}
|
||||
SOURCE_DATE=${{ steps.source.outputs.date }}
|
||||
FLUXER_APP_PROXY_TIME_FREEZE_ENABLED=false
|
||||
APP_ASSETS_REF=ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy-self-hosted:${{ needs.meta.outputs.build_version }}-assets
|
||||
APP_ASSETS_PLATFORM=linux/amd64
|
||||
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy-self-hosted:buildcache-${{ matrix.platform }}
|
||||
|
||||
Generated
-10
@@ -1755,16 +1755,6 @@ dependencies = [
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluxer-content-update-frozen-snapshot"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.23.1",
|
||||
"sha2 0.11.0",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluxer-dev"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -9,7 +9,6 @@ members = [
|
||||
"fluxer_messages",
|
||||
"fluxer_snowflakes",
|
||||
"tools/ci",
|
||||
"tools/content/update-frozen-snapshot",
|
||||
"tools/dev",
|
||||
"tools/i18n_auto",
|
||||
"fluxer_users",
|
||||
|
||||
@@ -23,6 +23,3 @@ tower-http = { version = "0.7.1", features = ["compression-gzip", "trace"] }
|
||||
tracing = "0.1.44"
|
||||
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||
|
||||
[features]
|
||||
default = ["time-freeze"]
|
||||
time-freeze = []
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
ARG BUILD_VERSION=""
|
||||
ARG PUBLIC_ASSET_BASE_URL=""
|
||||
ARG FLUXER_APP_PROXY_TIME_FREEZE_ENABLED="true"
|
||||
ARG BUNDLE_LOCAL_ASSETS="true"
|
||||
ARG APP_ASSETS_REF=app-assets
|
||||
ARG APP_ASSETS_PLATFORM=$BUILDPLATFORM
|
||||
@@ -111,7 +110,6 @@ FROM --platform=${APP_ASSETS_PLATFORM} ${APP_ASSETS_REF} AS app-static
|
||||
# ---------- Stage 3: Build the Rust proxy binary ----------
|
||||
FROM rust:1-trixie AS rust-builder
|
||||
|
||||
ARG FLUXER_APP_PROXY_TIME_FREEZE_ENABLED="true"
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
@@ -138,11 +136,7 @@ RUN printf '%s\n' \
|
||||
'strip = "symbols"' \
|
||||
> Cargo.toml
|
||||
|
||||
RUN if [ "${FLUXER_APP_PROXY_TIME_FREEZE_ENABLED}" = "false" ]; then \
|
||||
cargo build --release -p fluxer_app_proxy --no-default-features; \
|
||||
else \
|
||||
cargo build --release -p fluxer_app_proxy; \
|
||||
fi \
|
||||
RUN cargo build --release -p fluxer_app_proxy \
|
||||
&& cp target/release/fluxer_app_proxy /usr/local/bin/fluxer-app-proxy
|
||||
|
||||
# ---------- Stage 4: Runtime ----------
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
variable "BUILD_VERSION" { default = "" }
|
||||
variable "PUBLIC_ASSET_BASE_URL" { default = "" }
|
||||
variable "FLUXER_APP_PROXY_TIME_FREEZE_ENABLED" { default = "true" }
|
||||
variable "BUNDLE_LOCAL_ASSETS" { default = "true" }
|
||||
variable "IMAGE_REPO" { default = "" }
|
||||
variable "CACHE_FROM" { default = "" }
|
||||
@@ -26,7 +25,6 @@ target "app-proxy" {
|
||||
args = {
|
||||
BUILD_VERSION = BUILD_VERSION
|
||||
PUBLIC_ASSET_BASE_URL = PUBLIC_ASSET_BASE_URL
|
||||
FLUXER_APP_PROXY_TIME_FREEZE_ENABLED = FLUXER_APP_PROXY_TIME_FREEZE_ENABLED
|
||||
BUNDLE_LOCAL_ASSETS = BUNDLE_LOCAL_ASSETS
|
||||
APP_ASSETS_REF = APP_ASSETS_REF
|
||||
APP_ASSETS_PLATFORM = APP_ASSETS_PLATFORM
|
||||
|
||||
@@ -348,7 +348,6 @@ pub struct AppProxyConfig {
|
||||
pub discovery_upstream_url: String,
|
||||
pub discovery_refresh_interval_ms: u64,
|
||||
pub release_channel: ReleaseChannel,
|
||||
pub time_freeze_enabled: bool,
|
||||
pub build_version: String,
|
||||
pub bootstrap_api_endpoint: String,
|
||||
pub bootstrap_api_public_endpoint: Option<String>,
|
||||
@@ -445,7 +444,6 @@ impl AppProxyConfig {
|
||||
&["RELEASE_CHANNEL"],
|
||||
"stable",
|
||||
));
|
||||
let time_freeze_enabled = resolve_time_freeze_enabled_from_env();
|
||||
let geoip_source = cfg::parse_geoip_source_config(
|
||||
&cfg::read_first_env(&["FLUXER_GEOIP_DB_PATH", "MAXMIND_DB_PATH"], ""),
|
||||
"app_proxy",
|
||||
@@ -489,7 +487,6 @@ impl AppProxyConfig {
|
||||
60_000u64,
|
||||
),
|
||||
release_channel,
|
||||
time_freeze_enabled,
|
||||
build_version: cfg::read_env_preferred(
|
||||
&["BUILD_VERSION", "FLUXER_BUILD_VERSION"],
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
@@ -522,10 +519,6 @@ fn resolve_discovery_upstream_url_from_env() -> String {
|
||||
resolve_discovery_upstream_url(|name| env::var(name).ok())
|
||||
}
|
||||
|
||||
fn resolve_time_freeze_enabled_from_env() -> bool {
|
||||
resolve_time_freeze_enabled(|name| env::var(name).ok())
|
||||
}
|
||||
|
||||
fn resolve_bootstrap_api_public_endpoint_from_env() -> Option<String> {
|
||||
resolve_bootstrap_api_public_endpoint(|name| env::var(name).ok())
|
||||
.unwrap_or_else(|error| panic!("{error}"))
|
||||
@@ -550,24 +543,6 @@ where
|
||||
)))
|
||||
}
|
||||
|
||||
fn resolve_time_freeze_enabled<F>(mut read_var: F) -> bool
|
||||
where
|
||||
F: FnMut(&str) -> Option<String>,
|
||||
{
|
||||
if let Some(value) = read_var("FLUXER_APP_PROXY_TIME_FREEZE_ENABLED") {
|
||||
return parse_boolish(&value);
|
||||
}
|
||||
|
||||
!read_var("FLUXER_SELF_HOSTED").is_some_and(|value| parse_boolish(&value))
|
||||
}
|
||||
|
||||
fn parse_boolish(value: &str) -> bool {
|
||||
matches!(
|
||||
value.trim().to_ascii_lowercase().as_str(),
|
||||
"1" | "true" | "yes" | "on"
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_discovery_upstream_url<F>(mut read_var: F) -> String
|
||||
where
|
||||
F: FnMut(&str) -> Option<String>,
|
||||
@@ -613,11 +588,6 @@ mod tests {
|
||||
resolve_discovery_upstream_url(|name| env.get(name).map(|value| value.to_string()))
|
||||
}
|
||||
|
||||
fn resolve_time_freeze_from_pairs(pairs: &[(&str, &str)]) -> bool {
|
||||
let env: HashMap<&str, &str> = pairs.iter().copied().collect();
|
||||
resolve_time_freeze_enabled(|name| env.get(name).map(|value| value.to_string()))
|
||||
}
|
||||
|
||||
fn resolve_bootstrap_endpoint_from_pairs(pairs: &[(&str, &str)]) -> Option<String> {
|
||||
try_resolve_bootstrap_endpoint_from_pairs(pairs).expect("the boot html endpoint resolves")
|
||||
}
|
||||
@@ -817,29 +787,4 @@ mod tests {
|
||||
DEFAULT_DISCOVERY_UPSTREAM_URL
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_freeze_enabled_by_default_for_hosted_runtime() {
|
||||
assert!(resolve_time_freeze_from_pairs(&[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_freeze_disabled_by_default_for_self_hosted_runtime() {
|
||||
assert!(!resolve_time_freeze_from_pairs(&[(
|
||||
"FLUXER_SELF_HOSTED",
|
||||
"true"
|
||||
)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_time_freeze_setting_overrides_self_hosted_default() {
|
||||
assert!(resolve_time_freeze_from_pairs(&[
|
||||
("FLUXER_SELF_HOSTED", "true"),
|
||||
("FLUXER_APP_PROXY_TIME_FREEZE_ENABLED", "true"),
|
||||
]));
|
||||
assert!(!resolve_time_freeze_from_pairs(&[
|
||||
("FLUXER_SELF_HOSTED", "false"),
|
||||
("FLUXER_APP_PROXY_TIME_FREEZE_ENABLED", "false"),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,371 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::time_freeze::FrozenSnapshot;
|
||||
use base64::Engine;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
const STABLE_SNAPSHOT_SHA: &str = "3250c73cbf94e97e8b9ca5a0ecb03c20bdde3867";
|
||||
|
||||
const STABLE_INDEX_HTML: &str = "\
|
||||
PCFkb2N0eXBlIGh0bWw+PGh0bWwgbGFuZz0iZW4iPjxoZWFkPjxtZXRhIGNoYXJzZXQ9InV0Zi04Ij48dGl0bGU+Rmx1eGVyPC90\
|
||||
aXRsZT48bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLGluaXRpYWwtc2NhbGU9MSx2aWV3\
|
||||
cG9ydC1maXQ9Y292ZXIsaW50ZXJhY3RpdmUtd2lkZ2V0PXJlc2l6ZXMtY29udGVudCxtYXhpbXVtLXNjYWxlPTEsdXNlci1zY2Fs\
|
||||
YWJsZT1ubyI+PG1ldGEgbmFtZT0iZGVzY3JpcHRpb24iIGNvbnRlbnQ9IkZsdXhlciBpcyBhIGZyZWUgYW5kIG9wZW4gc291cmNl\
|
||||
IGluc3RhbnQgbWVzc2FnaW5nIGFuZCBWb0lQIHBsYXRmb3JtIGJ1aWx0IGZvciBmcmllbmRzLCBncm91cHMsIGFuZCBjb21tdW5p\
|
||||
dGllcy4iPjxsaW5rIHJlbD0icHJlY29ubmVjdCIgaHJlZj0iaHR0cHM6Ly9mbHV4ZXJzdGF0aWMuY29tIj48bGluayByZWw9InN0\
|
||||
eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vZmx1eGVyc3RhdGljLmNvbS9mb250cy9pYm0tcGxleC5jc3MiPjxsaW5rIHJlbD0ibWFu\
|
||||
aWZlc3QiIGhyZWY9Ii9tYW5pZmVzdC5qc29uIj48bGluayByZWw9Imljb24iIHR5cGU9ImltYWdlL3BuZyIgc2l6ZXM9IjMyeDMy\
|
||||
IiBocmVmPSJodHRwczovL2ZsdXhlcnN0YXRpYy5jb20vd2ViL2Zhdmljb24tMzJ4MzIucG5nIj48bGluayByZWw9ImFwcGxlLXRv\
|
||||
dWNoLWljb24iIHNpemVzPSIxODB4MTgwIiBocmVmPSJodHRwczovL2ZsdXhlcnN0YXRpYy5jb20vd2ViL2FwcGxlLXRvdWNoLWlj\
|
||||
b24ucG5nIj48bWV0YSBuYW1lPSJhcHBsZS1tb2JpbGUtd2ViLWFwcC1jYXBhYmxlIiBjb250ZW50PSJ5ZXMiPjxtZXRhIG5hbWU9\
|
||||
ImFwcGxlLW1vYmlsZS13ZWItYXBwLXN0YXR1cy1iYXItc3R5bGUiIGNvbnRlbnQ9ImRlZmF1bHQiPjxtZXRhIG5hbWU9ImFwcGxl\
|
||||
LW1vYmlsZS13ZWItYXBwLXRpdGxlIiBjb250ZW50PSJGbHV4ZXIiPjxtZXRhIG5hbWU9Im1vYmlsZS13ZWItYXBwLWNhcGFibGUi\
|
||||
IGNvbnRlbnQ9InllcyI+PG1ldGEgbmFtZT0idGhlbWUtY29sb3IiIGNvbnRlbnQ9IiM0NjQxRDkiPjxtZXRhIG5hbWU9Im1zYXBw\
|
||||
bGljYXRpb24tbmF2YnV0dG9uLWNvbG9yIiBjb250ZW50PSIjNDY0MUQ5Ij48bWV0YSBuYW1lPSJtc2FwcGxpY2F0aW9uLVRpbGVD\
|
||||
b2xvciIgY29udGVudD0iIzQ2NDFEOSI+PG1ldGEgbmFtZT0iZm9ybWF0LWRldGVjdGlvbiIgY29udGVudD0idGVsZXBob25lPW5v\
|
||||
Ij48bWV0YSBuYW1lPSJkaXNwbGF5IiBjb250ZW50PSJzdGFuZGFsb25lIj48c2NyaXB0IG5vbmNlPSJ7e0NTUF9OT05DRV9QTEFD\
|
||||
RUhPTERFUn19Ij4hZnVuY3Rpb24oKXt0cnl7dmFyIGU9d2luZG93LmxvY2F0aW9uO2lmKCIvIj09PWUucGF0aG5hbWUpe3ZhciBh\
|
||||
PSIvY2hhbm5lbHMvQG1lIjtlLnNlYXJjaCYmKGErPWUuc2VhcmNoKSxlLmhhc2gmJihhKz1lLmhhc2gpLGUucmVwbGFjZShhKX19\
|
||||
Y2F0Y2goZSl7fX0oKSxmdW5jdGlvbigpe3RyeXt2YXIgZT1sb2NhbFN0b3JhZ2UuZ2V0SXRlbSgidGhlbWUiKTtlJiZkb2N1bWVu\
|
||||
dC5kb2N1bWVudEVsZW1lbnQuY2xhc3NMaXN0LmFkZCgidGhlbWUtIitlKX1jYXRjaHt9fSgpPC9zY3JpcHQ+PGxpbmsgaHJlZj0i\
|
||||
aHR0cHM6Ly9mbHV4ZXJzdGF0aWMuY29tL2Fzc2V0cy9iOTNiNDgxOTU4OTA5N2NlLmNzcyIgcmVsPSJzdHlsZXNoZWV0Ij48bGlu\
|
||||
ayBocmVmPSJodHRwczovL2ZsdXhlcnN0YXRpYy5jb20vYXNzZXRzL2YzNzMyZTk1YzczYmQ3N2MuY3NzIiByZWw9InN0eWxlc2hl\
|
||||
ZXQiPjwvaGVhZD48Ym9keT48ZGl2IGlkPSJyb290Ij48L2Rpdj4KPG5vc2NyaXB0PkphdmFTY3JpcHQgaXMgcmVxdWlyZWQgdG8g\
|
||||
dXNlIHRoaXMgYXBwbGljYXRpb24uPC9ub3NjcmlwdD4KCgo8c2NyaXB0IHNyYz0iaHR0cHM6Ly9mbHV4ZXJzdGF0aWMuY29tL2Fz\
|
||||
c2V0cy84N2JiZWI3ZDZlMzY2YjNkLmpzIiB0eXBlPSJtb2R1bGUiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSJodHRwczovL2ZsdXhl\
|
||||
cnN0YXRpYy5jb20vYXNzZXRzL2JlMWRlM2U0OWI1NWM2ZWEuanMiIHR5cGU9Im1vZHVsZSI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9\
|
||||
Imh0dHBzOi8vZmx1eGVyc3RhdGljLmNvbS9hc3NldHMvMWY0OWE0NzEyMTg5OTlhZC5qcyIgdHlwZT0ibW9kdWxlIj48L3Njcmlw\
|
||||
dD48c2NyaXB0IHNyYz0iaHR0cHM6Ly9mbHV4ZXJzdGF0aWMuY29tL2Fzc2V0cy8wYmY4ZDMwZDM2ZTdjMzk0LmpzIiB0eXBlPSJt\
|
||||
b2R1bGUiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSJodHRwczovL2ZsdXhlcnN0YXRpYy5jb20vYXNzZXRzLzY0OTAzOTNlYTEyODc4\
|
||||
NmYuanMiIHR5cGU9Im1vZHVsZSI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vZmx1eGVyc3RhdGljLmNvbS9hc3NldHMv\
|
||||
YTJmYzQ2YTM2OTE2OGMxNy5qcyIgdHlwZT0ibW9kdWxlIj48L3NjcmlwdD48c2NyaXB0IHNyYz0iaHR0cHM6Ly9mbHV4ZXJzdGF0\
|
||||
aWMuY29tL2Fzc2V0cy82ZGNkMzI5MTkxNjJhMzZiLmpzIiB0eXBlPSJtb2R1bGUiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSJodHRw\
|
||||
czovL2ZsdXhlcnN0YXRpYy5jb20vYXNzZXRzLzNiYzNhNThiM2M3MDJhYmEuanMiIHR5cGU9Im1vZHVsZSI+PC9zY3JpcHQ+PHNj\
|
||||
cmlwdCBzcmM9Imh0dHBzOi8vZmx1eGVyc3RhdGljLmNvbS9hc3NldHMvM2NhYWY1MzA0ODI3ODUwOC5qcyIgdHlwZT0ibW9kdWxl\
|
||||
Ij48L3NjcmlwdD48c2NyaXB0IHNyYz0iaHR0cHM6Ly9mbHV4ZXJzdGF0aWMuY29tL2Fzc2V0cy8xM2EwZDZjY2I3YzllZjFjLmpz\
|
||||
IiB0eXBlPSJtb2R1bGUiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSJodHRwczovL2ZsdXhlcnN0YXRpYy5jb20vYXNzZXRzL2NlZGE5\
|
||||
MTIyZWMyMDI4OTIuanMiIHR5cGU9Im1vZHVsZSI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vZmx1eGVyc3RhdGljLmNv\
|
||||
bS9hc3NldHMvYTcyYTQzNzIyMTljNzNlYi5qcyIgdHlwZT0ibW9kdWxlIj48L3NjcmlwdD48c2NyaXB0IHNyYz0iaHR0cHM6Ly9m\
|
||||
bHV4ZXJzdGF0aWMuY29tL2Fzc2V0cy80N2JmYzQ5Nzg3Yjc5MDQ4LmpzIiB0eXBlPSJtb2R1bGUiPjwvc2NyaXB0PjxzY3JpcHQg\
|
||||
c3JjPSJodHRwczovL2ZsdXhlcnN0YXRpYy5jb20vYXNzZXRzL2M2ZjEyY2Q5NjA3NzE5OTUuanMiIHR5cGU9Im1vZHVsZSI+PC9z\
|
||||
Y3JpcHQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vZmx1eGVyc3RhdGljLmNvbS9hc3NldHMvMjNlOGZiZTIzMDdjMWFmNS5qcyIgdHlw\
|
||||
ZT0ibW9kdWxlIj48L3NjcmlwdD48c2NyaXB0IHNyYz0iaHR0cHM6Ly9mbHV4ZXJzdGF0aWMuY29tL2Fzc2V0cy9hMTkxYjQyNmI1\
|
||||
MWU5MjA4LmpzIiB0eXBlPSJtb2R1bGUiPjwvc2NyaXB0PjxzY3JpcHQgc3JjPSJodHRwczovL2ZsdXhlcnN0YXRpYy5jb20vYXNz\
|
||||
ZXRzLzRlZjY0NDEzYzhjZTVhMzYuanMiIHR5cGU9Im1vZHVsZSI+PC9zY3JpcHQ+PHNjcmlwdCBzcmM9Imh0dHBzOi8vZmx1eGVy\
|
||||
c3RhdGljLmNvbS9hc3NldHMvMzI1MGM3M2NiZjk0ZTk3ZS5qcyIgdHlwZT0ibW9kdWxlIj48L3NjcmlwdD48L2JvZHk+PC9odG1s\
|
||||
Pg==\
|
||||
";
|
||||
const STABLE_SW_JS: &str = "\
|
||||
InVzZSBzdHJpY3QiOwooKCkgPT4gewogIC8vIHNyYy9saWIvU2VydmljZVdvcmtlckxvZ0RCLnRzeAogIHZhciBTV19MT0dfREJf\
|
||||
TkFNRSA9ICJGbHV4ZXJTd0xvZ3MiOwogIHZhciBTV19MT0dfREJfVkVSU0lPTiA9IDE7CiAgdmFyIExPR1NfU1RPUkUgPSAibG9n\
|
||||
cyI7CiAgdmFyIE1FVEFfU1RPUkUgPSAibWV0YSI7CiAgdmFyIEVOQUJMRURfS0VZID0gImVuYWJsZWQiOwogIHZhciBNQVhfTE9H\
|
||||
UyA9IDJlMzsKICB2YXIgVFJJTV9UUklHR0VSID0gMjIwMDsKICB2YXIgaWRiQXZhaWxhYmxlID0gKCkgPT4gdHlwZW9mIGluZGV4\
|
||||
ZWREQiAhPT0gInVuZGVmaW5lZCI7CiAgdmFyIGRiUHJvbWlzZSA9IG51bGw7CiAgdmFyIG9wZW5EYiA9ICgpID0+IHsKICAgIGlm\
|
||||
IChkYlByb21pc2UpIHJldHVybiBkYlByb21pc2U7CiAgICBpZiAoIWlkYkF2YWlsYWJsZSgpKSB7CiAgICAgIHJldHVybiBQcm9t\
|
||||
aXNlLnJlamVjdChuZXcgRXJyb3IoIkluZGV4ZWREQiB1bmF2YWlsYWJsZSIpKTsKICAgIH0KICAgIGRiUHJvbWlzZSA9IG5ldyBQ\
|
||||
cm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsKICAgICAgbGV0IHJlcTsKICAgICAgdHJ5IHsKICAgICAgICByZXEgPSBpbmRl\
|
||||
eGVkREIub3BlbihTV19MT0dfREJfTkFNRSwgU1dfTE9HX0RCX1ZFUlNJT04pOwogICAgICB9IGNhdGNoIChlcnIpIHsKICAgICAg\
|
||||
ICByZWplY3QoZXJyKTsKICAgICAgICByZXR1cm47CiAgICAgIH0KICAgICAgcmVxLm9udXBncmFkZW5lZWRlZCA9ICgpID0+IHsK\
|
||||
ICAgICAgICBjb25zdCBkYiA9IHJlcS5yZXN1bHQ7CiAgICAgICAgaWYgKCFkYi5vYmplY3RTdG9yZU5hbWVzLmNvbnRhaW5zKExP\
|
||||
R1NfU1RPUkUpKSB7CiAgICAgICAgICBjb25zdCBzdG9yZSA9IGRiLmNyZWF0ZU9iamVjdFN0b3JlKExPR1NfU1RPUkUsIHsga2V5\
|
||||
UGF0aDogImlkIiwgYXV0b0luY3JlbWVudDogdHJ1ZSB9KTsKICAgICAgICAgIHN0b3JlLmNyZWF0ZUluZGV4KCJ0cyIsICJ0cyIp\
|
||||
OwogICAgICAgIH0KICAgICAgICBpZiAoIWRiLm9iamVjdFN0b3JlTmFtZXMuY29udGFpbnMoTUVUQV9TVE9SRSkpIHsKICAgICAg\
|
||||
ICAgIGRiLmNyZWF0ZU9iamVjdFN0b3JlKE1FVEFfU1RPUkUpOwogICAgICAgIH0KICAgICAgfTsKICAgICAgcmVxLm9uc3VjY2Vz\
|
||||
cyA9ICgpID0+IHsKICAgICAgICBjb25zdCBkYiA9IHJlcS5yZXN1bHQ7CiAgICAgICAgZGIub25jbG9zZSA9ICgpID0+IHsKICAg\
|
||||
ICAgICAgIGRiUHJvbWlzZSA9IG51bGw7CiAgICAgICAgfTsKICAgICAgICBkYi5vbnZlcnNpb25jaGFuZ2UgPSAoKSA9PiB7CiAg\
|
||||
ICAgICAgICB0cnkgewogICAgICAgICAgICBkYi5jbG9zZSgpOwogICAgICAgICAgfSBjYXRjaCB7CiAgICAgICAgICB9CiAgICAg\
|
||||
ICAgICBkYlByb21pc2UgPSBudWxsOwogICAgICAgIH07CiAgICAgICAgcmVzb2x2ZShkYik7CiAgICAgIH07CiAgICAgIHJlcS5v\
|
||||
bmVycm9yID0gKCkgPT4gcmVqZWN0KHJlcS5lcnJvciA/PyBuZXcgRXJyb3IoIkluZGV4ZWREQiBvcGVuIGZhaWxlZCIpKTsKICAg\
|
||||
ICAgcmVxLm9uYmxvY2tlZCA9ICgpID0+IHJlamVjdChuZXcgRXJyb3IoIkluZGV4ZWREQiBvcGVuIGJsb2NrZWQiKSk7CiAgICB9\
|
||||
KS5jYXRjaCgoZXJyKSA9PiB7CiAgICAgIGRiUHJvbWlzZSA9IG51bGw7CiAgICAgIHRocm93IGVycjsKICAgIH0pOwogICAgcmV0\
|
||||
dXJuIGRiUHJvbWlzZTsKICB9OwogIHZhciBzZXJpYWxpemVEYXRhID0gKHZhbHVlLCBkZXB0aCA9IDApID0+IHsKICAgIGlmICh2\
|
||||
YWx1ZSA9PSBudWxsKSByZXR1cm4gdmFsdWU7CiAgICBjb25zdCB0ID0gdHlwZW9mIHZhbHVlOwogICAgaWYgKHQgPT09ICJzdHJp\
|
||||
bmciIHx8IHQgPT09ICJudW1iZXIiIHx8IHQgPT09ICJib29sZWFuIikgcmV0dXJuIHZhbHVlOwogICAgaWYgKHQgPT09ICJiaWdp\
|
||||
bnQiKSByZXR1cm4gdmFsdWUudG9TdHJpbmcoKTsKICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIEVycm9yKSB7CiAgICAgIHJldHVy\
|
||||
biB7IF9fdHlwZTogIkVycm9yIiwgbmFtZTogdmFsdWUubmFtZSwgbWVzc2FnZTogdmFsdWUubWVzc2FnZSwgc3RhY2s6IHZhbHVl\
|
||||
LnN0YWNrIH07CiAgICB9CiAgICBpZiAodmFsdWUgaW5zdGFuY2VvZiBEYXRlKSByZXR1cm4gdmFsdWUudG9JU09TdHJpbmcoKTsK\
|
||||
ICAgIGlmIChkZXB0aCA+IDQpIHJldHVybiAiW3RydW5jYXRlZF0iOwogICAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSB7CiAg\
|
||||
ICAgIHJldHVybiB2YWx1ZS5zbGljZSgwLCA1MCkubWFwKChpdGVtKSA9PiBzZXJpYWxpemVEYXRhKGl0ZW0sIGRlcHRoICsgMSkp\
|
||||
OwogICAgfQogICAgaWYgKHQgPT09ICJvYmplY3QiKSB7CiAgICAgIGNvbnN0IG91dCA9IHt9OwogICAgICBsZXQgY291bnQgPSAw\
|
||||
OwogICAgICBmb3IgKGNvbnN0IGtleSBvZiBPYmplY3Qua2V5cyh2YWx1ZSkpIHsKICAgICAgICBpZiAoY291bnQgPj0gNTApIHsK\
|
||||
ICAgICAgICAgIG91dFsiX190cnVuY2F0ZWQiXSA9IHRydWU7CiAgICAgICAgICBicmVhazsKICAgICAgICB9CiAgICAgICAgdHJ5\
|
||||
IHsKICAgICAgICAgIG91dFtrZXldID0gc2VyaWFsaXplRGF0YSh2YWx1ZVtrZXldLCBkZXB0aCArIDEpOwogICAgICAgIH0gY2F0\
|
||||
Y2ggewogICAgICAgICAgb3V0W2tleV0gPSAiW3Vuc2VyaWFsaXphYmxlXSI7CiAgICAgICAgfQogICAgICAgIGNvdW50ICs9IDE7\
|
||||
CiAgICAgIH0KICAgICAgcmV0dXJuIG91dDsKICAgIH0KICAgIHJldHVybiBgWyR7dH1dYDsKICB9OwogIGFzeW5jIGZ1bmN0aW9u\
|
||||
IGFwcGVuZFN3TG9nKGxldmVsLCBzb3VyY2UsIG1lc3NhZ2UsIGRhdGEpIHsKICAgIGlmICghaWRiQXZhaWxhYmxlKCkpIHJldHVy\
|
||||
bjsKICAgIGxldCBkYjsKICAgIHRyeSB7CiAgICAgIGRiID0gYXdhaXQgb3BlbkRiKCk7CiAgICB9IGNhdGNoIHsKICAgICAgcmV0\
|
||||
dXJuOwogICAgfQogICAgY29uc3QgcmVjb3JkID0gewogICAgICB0czogRGF0ZS5ub3coKSwKICAgICAgbGV2ZWwsCiAgICAgIHNv\
|
||||
dXJjZSwKICAgICAgbWVzc2FnZSwKICAgICAgZGF0YTogZGF0YSA9PT0gdm9pZCAwID8gdm9pZCAwIDogc2VyaWFsaXplRGF0YShk\
|
||||
YXRhKQogICAgfTsKICAgIHRyeSB7CiAgICAgIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsKICAgICAg\
|
||||
ICBjb25zdCB0eCA9IGRiLnRyYW5zYWN0aW9uKExPR1NfU1RPUkUsICJyZWFkd3JpdGUiKTsKICAgICAgICB0eC5vbmNvbXBsZXRl\
|
||||
ID0gKCkgPT4gcmVzb2x2ZSgpOwogICAgICAgIHR4Lm9uZXJyb3IgPSAoKSA9PiByZWplY3QodHguZXJyb3IpOwogICAgICAgIHR4\
|
||||
Lm9uYWJvcnQgPSAoKSA9PiByZWplY3QodHguZXJyb3IpOwogICAgICAgIHR4Lm9iamVjdFN0b3JlKExPR1NfU1RPUkUpLmFkZChy\
|
||||
ZWNvcmQpOwogICAgICB9KTsKICAgIH0gY2F0Y2ggewogICAgICByZXR1cm47CiAgICB9CiAgICB2b2lkIHNjaGVkdWxlVHJpbSgp\
|
||||
OwogIH0KICB2YXIgdHJpbVNjaGVkdWxlZCA9IGZhbHNlOwogIHZhciBzY2hlZHVsZVRyaW0gPSBhc3luYyAoKSA9PiB7CiAgICBp\
|
||||
ZiAodHJpbVNjaGVkdWxlZCkgcmV0dXJuOwogICAgdHJpbVNjaGVkdWxlZCA9IHRydWU7CiAgICB0cnkgewogICAgICBhd2FpdCB0\
|
||||
cmltSWZOZWVkZWQoKTsKICAgIH0gZmluYWxseSB7CiAgICAgIHRyaW1TY2hlZHVsZWQgPSBmYWxzZTsKICAgIH0KICB9OwogIGFz\
|
||||
eW5jIGZ1bmN0aW9uIHRyaW1JZk5lZWRlZCgpIHsKICAgIGxldCBkYjsKICAgIHRyeSB7CiAgICAgIGRiID0gYXdhaXQgb3BlbkRi\
|
||||
KCk7CiAgICB9IGNhdGNoIHsKICAgICAgcmV0dXJuOwogICAgfQogICAgbGV0IGNvdW50ID0gMDsKICAgIHRyeSB7CiAgICAgIGNv\
|
||||
dW50ID0gYXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4gewogICAgICAgIGNvbnN0IHR4ID0gZGIudHJhbnNh\
|
||||
Y3Rpb24oTE9HU19TVE9SRSwgInJlYWRvbmx5Iik7CiAgICAgICAgY29uc3QgcmVxID0gdHgub2JqZWN0U3RvcmUoTE9HU19TVE9S\
|
||||
RSkuY291bnQoKTsKICAgICAgICByZXEub25zdWNjZXNzID0gKCkgPT4gcmVzb2x2ZShyZXEucmVzdWx0KTsKICAgICAgICByZXEu\
|
||||
b25lcnJvciA9ICgpID0+IHJlamVjdChyZXEuZXJyb3IpOwogICAgICB9KTsKICAgIH0gY2F0Y2ggewogICAgICByZXR1cm47CiAg\
|
||||
ICB9CiAgICBpZiAoY291bnQgPCBUUklNX1RSSUdHRVIpIHJldHVybjsKICAgIGNvbnN0IHRvRGVsZXRlID0gY291bnQgLSBNQVhf\
|
||||
TE9HUzsKICAgIHRyeSB7CiAgICAgIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHsKICAgICAgICBjb25z\
|
||||
dCB0eCA9IGRiLnRyYW5zYWN0aW9uKExPR1NfU1RPUkUsICJyZWFkd3JpdGUiKTsKICAgICAgICB0eC5vbmNvbXBsZXRlID0gKCkg\
|
||||
PT4gcmVzb2x2ZSgpOwogICAgICAgIHR4Lm9uZXJyb3IgPSAoKSA9PiByZWplY3QodHguZXJyb3IpOwogICAgICAgIHR4Lm9uYWJv\
|
||||
cnQgPSAoKSA9PiByZWplY3QodHguZXJyb3IpOwogICAgICAgIGNvbnN0IHN0b3JlID0gdHgub2JqZWN0U3RvcmUoTE9HU19TVE9S\
|
||||
RSk7CiAgICAgICAgY29uc3QgY3Vyc29yUmVxID0gc3RvcmUuaW5kZXgoInRzIikub3BlbkN1cnNvcigpOwogICAgICAgIGxldCBk\
|
||||
ZWxldGVkID0gMDsKICAgICAgICBjdXJzb3JSZXEub25zdWNjZXNzID0gKCkgPT4gewogICAgICAgICAgY29uc3QgY3Vyc29yID0g\
|
||||
Y3Vyc29yUmVxLnJlc3VsdDsKICAgICAgICAgIGlmICghY3Vyc29yIHx8IGRlbGV0ZWQgPj0gdG9EZWxldGUpIHJldHVybjsKICAg\
|
||||
ICAgICAgIGN1cnNvci5kZWxldGUoKTsKICAgICAgICAgIGRlbGV0ZWQgKz0gMTsKICAgICAgICAgIGN1cnNvci5jb250aW51ZSgp\
|
||||
OwogICAgICAgIH07CiAgICAgICAgY3Vyc29yUmVxLm9uZXJyb3IgPSAoKSA9PiByZWplY3QoY3Vyc29yUmVxLmVycm9yKTsKICAg\
|
||||
ICAgfSk7CiAgICB9IGNhdGNoIHsKICAgIH0KICB9CiAgYXN5bmMgZnVuY3Rpb24gaXNTd0xvZ2dpbmdFbmFibGVkKCkgewogICAg\
|
||||
aWYgKCFpZGJBdmFpbGFibGUoKSkgcmV0dXJuIGZhbHNlOwogICAgbGV0IGRiOwogICAgdHJ5IHsKICAgICAgZGIgPSBhd2FpdCBv\
|
||||
cGVuRGIoKTsKICAgIH0gY2F0Y2ggewogICAgICByZXR1cm4gZmFsc2U7CiAgICB9CiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJl\
|
||||
c29sdmUpID0+IHsKICAgICAgY29uc3QgdHggPSBkYi50cmFuc2FjdGlvbihNRVRBX1NUT1JFLCAicmVhZG9ubHkiKTsKICAgICAg\
|
||||
Y29uc3QgcmVxID0gdHgub2JqZWN0U3RvcmUoTUVUQV9TVE9SRSkuZ2V0KEVOQUJMRURfS0VZKTsKICAgICAgcmVxLm9uc3VjY2Vz\
|
||||
cyA9ICgpID0+IHJlc29sdmUocmVxLnJlc3VsdCA9PT0gdHJ1ZSk7CiAgICAgIHJlcS5vbmVycm9yID0gKCkgPT4gcmVzb2x2ZShm\
|
||||
YWxzZSk7CiAgICB9KTsKICB9CiAgdmFyIFNXX0xPR19TRVRfRU5BQkxFRF9NRVNTQUdFID0gIlNXX0xPR19TRVRfRU5BQkxFRCI7\
|
||||
CgogIC8vIHNyYy9zZXJ2aWNlX3dvcmtlci9Xb3JrZXIudHN4CiAgdmFyIGxvZ2dpbmdFbmFibGVkID0gZmFsc2U7CiAgdmFyIGVu\
|
||||
c3VyZUxvZ2dpbmdFbmFibGVkTG9hZGVkID0gaXNTd0xvZ2dpbmdFbmFibGVkKCkudGhlbigKICAgICh2KSA9PiB7CiAgICAgIGxv\
|
||||
Z2dpbmdFbmFibGVkID0gdjsKICAgIH0sCiAgICAoKSA9PiB2b2lkIDAKICApOwogIHZhciBsb2cgPSBhc3luYyAobGV2ZWwsIG1l\
|
||||
c3NhZ2UsIGRhdGEpID0+IHsKICAgIHRyeSB7CiAgICAgIGlmICh0eXBlb2YgY29uc29sZSAhPT0gInVuZGVmaW5lZCIpIHsKICAg\
|
||||
ICAgICBjb25zdCBmbiA9IGxldmVsID09PSAiZXJyb3IiID8gY29uc29sZS5lcnJvciA6IGxldmVsID09PSAid2FybiIgPyBjb25z\
|
||||
b2xlLndhcm4gOiBsZXZlbCA9PT0gImluZm8iID8gY29uc29sZS5pbmZvIDogY29uc29sZS5kZWJ1ZzsKICAgICAgICBpZiAoZGF0\
|
||||
YSAhPT0gdm9pZCAwKSBmbj8uY2FsbChjb25zb2xlLCAiW1NXXSIsIG1lc3NhZ2UsIGRhdGEpOwogICAgICAgIGVsc2UgZm4/LmNh\
|
||||
bGwoY29uc29sZSwgIltTV10iLCBtZXNzYWdlKTsKICAgICAgfQogICAgfSBjYXRjaCB7CiAgICB9CiAgICBpZiAoIWxvZ2dpbmdF\
|
||||
bmFibGVkKSByZXR1cm47CiAgICB0cnkgewogICAgICBhd2FpdCBhcHBlbmRTd0xvZyhsZXZlbCwgInN3IiwgbWVzc2FnZSwgZGF0\
|
||||
YSk7CiAgICB9IGNhdGNoIHsKICAgIH0KICB9OwogIHNlbGYuYWRkRXZlbnRMaXN0ZW5lcigiaW5zdGFsbCIsIChldmVudCkgPT4g\
|
||||
ewogICAgZXZlbnQud2FpdFVudGlsKAogICAgICAoYXN5bmMgKCkgPT4gewogICAgICAgIGF3YWl0IGVuc3VyZUxvZ2dpbmdFbmFi\
|
||||
bGVkTG9hZGVkOwogICAgICAgIGF3YWl0IGxvZygiaW5mbyIsICJpbnN0YWxsIik7CiAgICAgICAgc2VsZi5za2lwV2FpdGluZygp\
|
||||
OwogICAgICB9KSgpCiAgICApOwogIH0pOwogIHNlbGYuYWRkRXZlbnRMaXN0ZW5lcigiYWN0aXZhdGUiLCAoZXZlbnQpID0+IHsK\
|
||||
ICAgIGV2ZW50LndhaXRVbnRpbCgKICAgICAgKGFzeW5jICgpID0+IHsKICAgICAgICBhd2FpdCBlbnN1cmVMb2dnaW5nRW5hYmxl\
|
||||
ZExvYWRlZDsKICAgICAgICBhd2FpdCBsb2coImluZm8iLCAiYWN0aXZhdGUiKTsKICAgICAgICBhd2FpdCBzZWxmLmNsaWVudHMu\
|
||||
Y2xhaW0oKTsKICAgICAgICBhd2FpdCBsb2coImRlYnVnIiwgImNsaWVudHMgY2xhaW1lZCIpOwogICAgICB9KSgpCiAgICApOwog\
|
||||
IH0pOwogIHNlbGYuYWRkRXZlbnRMaXN0ZW5lcigibWVzc2FnZSIsIChldmVudCkgPT4gewogICAgY29uc3QgZGF0YSA9IGV2ZW50\
|
||||
LmRhdGE7CiAgICBjb25zdCB0eXBlID0gZGF0YT8udHlwZTsKICAgIGlmICh0eXBlID09PSAiU0tJUF9XQUlUSU5HIikgewogICAg\
|
||||
ICBldmVudC53YWl0VW50aWwobG9nKCJpbmZvIiwgIm1lc3NhZ2U6IFNLSVBfV0FJVElORyIpKTsKICAgICAgc2VsZi5za2lwV2Fp\
|
||||
dGluZygpOwogICAgICByZXR1cm47CiAgICB9CiAgICBpZiAodHlwZSA9PT0gIkFQUF9VUERBVEVfQkFER0UiKSB7CiAgICAgIGNv\
|
||||
bnN0IHJhd0NvdW50ID0gZGF0YT8uY291bnQ7CiAgICAgIGxldCBiYWRnZUNvdW50ID0gbnVsbDsKICAgICAgaWYgKHR5cGVvZiBy\
|
||||
YXdDb3VudCA9PT0gIm51bWJlciIgJiYgTnVtYmVyLmlzRmluaXRlKHJhd0NvdW50KSkgewogICAgICAgIGJhZGdlQ291bnQgPSBy\
|
||||
YXdDb3VudDsKICAgICAgfSBlbHNlIGlmICh0eXBlb2YgcmF3Q291bnQgPT09ICJzdHJpbmciICYmIHJhd0NvdW50Lmxlbmd0aCA+\
|
||||
IDApIHsKICAgICAgICBjb25zdCBwYXJzZWQgPSBOdW1iZXIocmF3Q291bnQpOwogICAgICAgIGJhZGdlQ291bnQgPSBOdW1iZXIu\
|
||||
aXNGaW5pdGUocGFyc2VkKSA/IHBhcnNlZCA6IG51bGw7CiAgICAgIH0KICAgICAgZXZlbnQud2FpdFVudGlsKAogICAgICAgIChh\
|
||||
c3luYyAoKSA9PiB7CiAgICAgICAgICBhd2FpdCBsb2coImRlYnVnIiwgIm1lc3NhZ2U6IEFQUF9VUERBVEVfQkFER0UiLCB7IGJh\
|
||||
ZGdlQ291bnQgfSk7CiAgICAgICAgICBhd2FpdCB1cGRhdGVBcHBCYWRnZShiYWRnZUNvdW50KTsKICAgICAgICB9KSgpCiAgICAg\
|
||||
ICk7CiAgICAgIHJldHVybjsKICAgIH0KICAgIGlmICh0eXBlID09PSBTV19MT0dfU0VUX0VOQUJMRURfTUVTU0FHRSkgewogICAg\
|
||||
ICBjb25zdCBlbmFibGVkID0gZGF0YT8uZW5hYmxlZCA9PT0gdHJ1ZTsKICAgICAgbG9nZ2luZ0VuYWJsZWQgPSBlbmFibGVkOwog\
|
||||
ICAgICBldmVudC53YWl0VW50aWwobG9nKCJpbmZvIiwgYG1lc3NhZ2U6IGxvZ2dpbmcgJHtlbmFibGVkID8gImVuYWJsZWQiIDog\
|
||||
ImRpc2FibGVkIn1gKSk7CiAgICAgIHJldHVybjsKICAgIH0KICAgIGV2ZW50LndhaXRVbnRpbChsb2coImRlYnVnIiwgIm1lc3Nh\
|
||||
Z2U6IHVua25vd24iLCB7IHR5cGUgfSkpOwogIH0pOwogIHZhciBub3JtYWxpemVQdXNoUGF5bG9hZCA9IChyYXcpID0+IHsKICAg\
|
||||
IGNvbnN0IHBheWxvYWQgPSByYXcgPz8ge307CiAgICBpZiAocGF5bG9hZC53ZWJfcHVzaCA9PT0gODAzMCAmJiBwYXlsb2FkLm5v\
|
||||
dGlmaWNhdGlvbiAmJiB0eXBlb2YgcGF5bG9hZC5ub3RpZmljYXRpb24gPT09ICJvYmplY3QiKSB7CiAgICAgIGNvbnN0IGlubmVy\
|
||||
ID0gcGF5bG9hZC5ub3RpZmljYXRpb247CiAgICAgIGNvbnN0IG5hdmlnYXRlVXJsID0gaW5uZXIubmF2aWdhdGU7CiAgICAgIGNv\
|
||||
bnN0IGRhdGEgPSB7IC4uLmlubmVyLmRhdGEgPz8ge30gfTsKICAgICAgaWYgKHR5cGVvZiBuYXZpZ2F0ZVVybCA9PT0gInN0cmlu\
|
||||
ZyIgJiYgIWRhdGEudXJsKSBkYXRhLnVybCA9IG5hdmlnYXRlVXJsOwogICAgICByZXR1cm4geyAuLi5pbm5lciwgZGF0YSB9Owog\
|
||||
ICAgfQogICAgcmV0dXJuIHBheWxvYWQ7CiAgfTsKICB2YXIgZ2V0QmFkZ2VDb3VudCA9IChwYXlsb2FkKSA9PiB7CiAgICBjb25z\
|
||||
dCBiYWRnZVZhbHVlID0gcGF5bG9hZC5kYXRhPy5iYWRnZV9jb3VudDsKICAgIGlmICh0eXBlb2YgYmFkZ2VWYWx1ZSA9PT0gIm51\
|
||||
bWJlciIgJiYgTnVtYmVyLmlzRmluaXRlKGJhZGdlVmFsdWUpKSB7CiAgICAgIHJldHVybiBiYWRnZVZhbHVlOwogICAgfQogICAg\
|
||||
aWYgKHR5cGVvZiBiYWRnZVZhbHVlID09PSAic3RyaW5nIiAmJiBiYWRnZVZhbHVlLmxlbmd0aCA+IDApIHsKICAgICAgY29uc3Qg\
|
||||
cGFyc2VkID0gTnVtYmVyKGJhZGdlVmFsdWUpOwogICAgICByZXR1cm4gTnVtYmVyLmlzRmluaXRlKHBhcnNlZCkgPyBwYXJzZWQg\
|
||||
OiBudWxsOwogICAgfQogICAgcmV0dXJuIG51bGw7CiAgfTsKICB2YXIgdXBkYXRlQXBwQmFkZ2UgPSBhc3luYyAoY291bnQpID0+\
|
||||
IHsKICAgIGlmICh0eXBlb2YgbmF2aWdhdG9yLnNldEFwcEJhZGdlICE9PSAiZnVuY3Rpb24iICYmIHR5cGVvZiBuYXZpZ2F0b3Iu\
|
||||
Y2xlYXJBcHBCYWRnZSAhPT0gImZ1bmN0aW9uIikgewogICAgICByZXR1cm47CiAgICB9CiAgICB0cnkgewogICAgICBpZiAoY291\
|
||||
bnQgIT09IG51bGwgJiYgY291bnQgPiAwKSB7CiAgICAgICAgaWYgKHR5cGVvZiBuYXZpZ2F0b3Iuc2V0QXBwQmFkZ2UgPT09ICJm\
|
||||
dW5jdGlvbiIpIHsKICAgICAgICAgIGF3YWl0IG5hdmlnYXRvci5zZXRBcHBCYWRnZShjb3VudCk7CiAgICAgICAgICBhd2FpdCBs\
|
||||
b2coImRlYnVnIiwgImJhZGdlIHNldCIsIHsgY291bnQgfSk7CiAgICAgICAgfQogICAgICB9IGVsc2UgaWYgKHR5cGVvZiBuYXZp\
|
||||
Z2F0b3IuY2xlYXJBcHBCYWRnZSA9PT0gImZ1bmN0aW9uIikgewogICAgICAgIGF3YWl0IG5hdmlnYXRvci5jbGVhckFwcEJhZGdl\
|
||||
KCk7CiAgICAgICAgYXdhaXQgbG9nKCJkZWJ1ZyIsICJiYWRnZSBjbGVhcmVkIik7CiAgICAgIH0KICAgIH0gY2F0Y2ggKGVycm9y\
|
||||
KSB7CiAgICAgIGF3YWl0IGxvZygiZXJyb3IiLCAiRmFpbGVkIHRvIHVwZGF0ZSBhcHAgYmFkZ2UiLCB7IGVycm9yOiBkZXNjcmli\
|
||||
ZUVycm9yKGVycm9yKSB9KTsKICAgIH0KICB9OwogIHZhciByZXNvbHZlVGFyZ2V0VXJsID0gKHVybCkgPT4gewogICAgaWYgKCF1\
|
||||
cmwpIHJldHVybiBudWxsOwogICAgdHJ5IHsKICAgICAgY29uc3QgcmVzb2x2ZWQgPSBuZXcgVVJMKHVybCwgc2VsZi5sb2NhdGlv\
|
||||
bi5vcmlnaW4pOwogICAgICBpZiAocmVzb2x2ZWQucHJvdG9jb2wgIT09ICJodHRwOiIgJiYgcmVzb2x2ZWQucHJvdG9jb2wgIT09\
|
||||
ICJodHRwczoiKSByZXR1cm4gbnVsbDsKICAgICAgcmV0dXJuIHJlc29sdmVkLnRvU3RyaW5nKCk7CiAgICB9IGNhdGNoIHsKICAg\
|
||||
ICAgcmV0dXJuIG51bGw7CiAgICB9CiAgfTsKICB2YXIgcG9zdE1lc3NhZ2VUb0NsaWVudHMgPSBhc3luYyAobWVzc2FnZSkgPT4g\
|
||||
ewogICAgdHJ5IHsKICAgICAgY29uc3QgY2xpZW50TGlzdCA9IGF3YWl0IHNlbGYuY2xpZW50cy5tYXRjaEFsbCh7CiAgICAgICAg\
|
||||
dHlwZTogIndpbmRvdyIsCiAgICAgICAgaW5jbHVkZVVuY29udHJvbGxlZDogdHJ1ZQogICAgICB9KTsKICAgICAgZm9yIChjb25z\
|
||||
dCBjbGllbnQgb2YgY2xpZW50TGlzdCkgewogICAgICAgIGNsaWVudC5wb3N0TWVzc2FnZShtZXNzYWdlKTsKICAgICAgfQogICAg\
|
||||
ICByZXR1cm4gY2xpZW50TGlzdDsKICAgIH0gY2F0Y2ggKGVycm9yKSB7CiAgICAgIGF3YWl0IGxvZygiZXJyb3IiLCAiVW5hYmxl\
|
||||
IHRvIGJyb2FkY2FzdCB0byBjbGllbnRzIiwgeyBlcnJvcjogZGVzY3JpYmVFcnJvcihlcnJvcikgfSk7CiAgICAgIHJldHVybiBb\
|
||||
XTsKICAgIH0KICB9OwogIHZhciBmb2N1c09yT3BlbkNsaWVudCA9IGFzeW5jICh0YXJnZXRVcmwsIHRhcmdldFVzZXJJZCkgPT4g\
|
||||
ewogICAgY29uc3QgbWVzc2FnZSA9IHsKICAgICAgdHlwZTogIk5PVElGSUNBVElPTl9DTElDS19OQVZJR0FURSIsCiAgICAgIHVy\
|
||||
bDogdGFyZ2V0VXJsCiAgICB9OwogICAgaWYgKHRhcmdldFVzZXJJZCkgewogICAgICBtZXNzYWdlLnRhcmdldFVzZXJJZCA9IHRh\
|
||||
cmdldFVzZXJJZDsKICAgIH0KICAgIGNvbnN0IGNsaWVudExpc3QgPSBhd2FpdCBwb3N0TWVzc2FnZVRvQ2xpZW50cyhtZXNzYWdl\
|
||||
KTsKICAgIGNvbnN0IGV4YWN0ID0gY2xpZW50TGlzdC5maW5kKChjKSA9PiBjLnVybCA9PT0gdGFyZ2V0VXJsKTsKICAgIGlmIChl\
|
||||
eGFjdCkgewogICAgICBhd2FpdCBsb2coImRlYnVnIiwgImZvY3VzIGV4YWN0IGNsaWVudCIsIHsgdXJsOiB0YXJnZXRVcmwgfSk7\
|
||||
CiAgICAgIGF3YWl0IGV4YWN0LmZvY3VzKCk7CiAgICAgIHJldHVybjsKICAgIH0KICAgIGNvbnN0IHNhbWVPcmlnaW4gPSBjbGll\
|
||||
bnRMaXN0LmZpbmQoKGMpID0+IHsKICAgICAgdHJ5IHsKICAgICAgICByZXR1cm4gbmV3IFVSTChjLnVybCkub3JpZ2luID09PSBz\
|
||||
ZWxmLmxvY2F0aW9uLm9yaWdpbjsKICAgICAgfSBjYXRjaCB7CiAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICB9CiAgICB9KTsK\
|
||||
ICAgIGlmIChzYW1lT3JpZ2luKSB7CiAgICAgIGF3YWl0IGxvZygiZGVidWciLCAiZm9jdXMgc2FtZS1vcmlnaW4gY2xpZW50Iiwg\
|
||||
eyB1cmw6IHNhbWVPcmlnaW4udXJsIH0pOwogICAgICBhd2FpdCBzYW1lT3JpZ2luLmZvY3VzKCk7CiAgICAgIHJldHVybjsKICAg\
|
||||
IH0KICAgIGlmIChzZWxmLmNsaWVudHMub3BlbldpbmRvdykgewogICAgICBhd2FpdCBsb2coImRlYnVnIiwgIm9wZW5XaW5kb3ci\
|
||||
LCB7IHVybDogdGFyZ2V0VXJsIH0pOwogICAgICBhd2FpdCBzZWxmLmNsaWVudHMub3BlbldpbmRvdyh0YXJnZXRVcmwpOwogICAg\
|
||||
fQogIH07CiAgdmFyIGRlc2NyaWJlRXJyb3IgPSAoZXJyb3IpID0+IHsKICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIEVycm9yKSB7\
|
||||
CiAgICAgIHJldHVybiB7IG5hbWU6IGVycm9yLm5hbWUsIG1lc3NhZ2U6IGVycm9yLm1lc3NhZ2UsIHN0YWNrOiBlcnJvci5zdGFj\
|
||||
ayB9OwogICAgfQogICAgcmV0dXJuIHsgdmFsdWU6IFN0cmluZyhlcnJvcikgfTsKICB9OwogIHNlbGYuYWRkRXZlbnRMaXN0ZW5l\
|
||||
cigicHVzaCIsIChldmVudCkgPT4gewogICAgZXZlbnQud2FpdFVudGlsKAogICAgICAoYXN5bmMgKCkgPT4gewogICAgICAgIGF3\
|
||||
YWl0IGVuc3VyZUxvZ2dpbmdFbmFibGVkTG9hZGVkOwogICAgICAgIGxldCByYXc7CiAgICAgICAgbGV0IHJhd1RleHQgPSBudWxs\
|
||||
OwogICAgICAgIHRyeSB7CiAgICAgICAgICByYXdUZXh0ID0gZXZlbnQuZGF0YT8udGV4dCgpID8/IG51bGw7CiAgICAgICAgfSBj\
|
||||
YXRjaCB7CiAgICAgICAgICByYXdUZXh0ID0gbnVsbDsKICAgICAgICB9CiAgICAgICAgdHJ5IHsKICAgICAgICAgIHJhdyA9IGV2\
|
||||
ZW50LmRhdGE/Lmpzb24/LigpOwogICAgICAgIH0gY2F0Y2ggKGVycm9yKSB7CiAgICAgICAgICBhd2FpdCBsb2coImVycm9yIiwg\
|
||||
InB1c2g6IEpTT04gcGFyc2UgZmFpbGVkIiwgewogICAgICAgICAgICBlcnJvcjogZGVzY3JpYmVFcnJvcihlcnJvciksCiAgICAg\
|
||||
ICAgICAgIHJhd1ByZXZpZXc6IHJhd1RleHQ/LnNsaWNlKDAsIDUxMikgPz8gbnVsbAogICAgICAgICAgfSk7CiAgICAgICAgfQog\
|
||||
ICAgICAgIGNvbnN0IHBheWxvYWQgPSBub3JtYWxpemVQdXNoUGF5bG9hZChyYXcgPz8geyB0aXRsZTogIkZsdXhlciIgfSk7CiAg\
|
||||
ICAgICAgY29uc3QgdGl0bGUgPSBwYXlsb2FkLnRpdGxlID8/ICJGbHV4ZXIiOwogICAgICAgIGNvbnN0IGNoYW5uZWxJZCA9IHBh\
|
||||
eWxvYWQuZGF0YT8uY2hhbm5lbF9pZDsKICAgICAgICBjb25zdCB0YWcgPSBwYXlsb2FkLnRhZyA/PyAodHlwZW9mIGNoYW5uZWxJ\
|
||||
ZCA9PT0gInN0cmluZyIgPyBgY2hhbm5lbDoke2NoYW5uZWxJZH1gIDogdm9pZCAwKTsKICAgICAgICBjb25zdCBib2R5ID0gcGF5\
|
||||
bG9hZC5ib2R5ID8/ICIiOwogICAgICAgIGNvbnN0IG9wdGlvbnMgPSB7CiAgICAgICAgICBib2R5LAogICAgICAgICAgaWNvbjog\
|
||||
cGF5bG9hZC5pY29uID8/IHZvaWQgMCwKICAgICAgICAgIGJhZGdlOiBwYXlsb2FkLmJhZGdlID8/IHZvaWQgMCwKICAgICAgICAg\
|
||||
IGRhdGE6IHBheWxvYWQuZGF0YSA/PyB2b2lkIDAsCiAgICAgICAgICB0YWcsCiAgICAgICAgICByZW5vdGlmeTogdGFnICE9PSB2\
|
||||
b2lkIDAKICAgICAgICB9OwogICAgICAgIGNvbnN0IGJhZGdlQ291bnQgPSBnZXRCYWRnZUNvdW50KHBheWxvYWQpOwogICAgICAg\
|
||||
IGF3YWl0IGxvZygiaW5mbyIsICJwdXNoIHJlY2VpdmVkIiwgewogICAgICAgICAgdGl0bGUsCiAgICAgICAgICBoYXNCb2R5OiBC\
|
||||
b29sZWFuKHBheWxvYWQuYm9keSksCiAgICAgICAgICB0YWcsCiAgICAgICAgICBoYXNEYXRhOiBwYXlsb2FkLmRhdGEgIT09IHZv\
|
||||
aWQgMCwKICAgICAgICAgIGJhZGdlQ291bnQsCiAgICAgICAgICB3ZWJQdXNoOiBwYXlsb2FkLndlYl9wdXNoID8/IG51bGwKICAg\
|
||||
ICAgICB9KTsKICAgICAgICB0cnkgewogICAgICAgICAgYXdhaXQgUHJvbWlzZS5hbGwoW3NlbGYucmVnaXN0cmF0aW9uLnNob3dO\
|
||||
b3RpZmljYXRpb24odGl0bGUsIG9wdGlvbnMpLCB1cGRhdGVBcHBCYWRnZShiYWRnZUNvdW50KV0pOwogICAgICAgIH0gY2F0Y2gg\
|
||||
KGVycm9yKSB7CiAgICAgICAgICBhd2FpdCBsb2coImVycm9yIiwgInB1c2g6IHNob3dOb3RpZmljYXRpb24gZmFpbGVkLCBzaG93\
|
||||
aW5nIGZhbGxiYWNrIiwgeyBlcnJvcjogZGVzY3JpYmVFcnJvcihlcnJvcikgfSk7CiAgICAgICAgICB0cnkgewogICAgICAgICAg\
|
||||
ICBhd2FpdCBzZWxmLnJlZ2lzdHJhdGlvbi5zaG93Tm90aWZpY2F0aW9uKCJGbHV4ZXIiLCB7IGJvZHk6ICIiIH0pOwogICAgICAg\
|
||||
ICAgfSBjYXRjaCAoZmFsbGJhY2tFcnJvcikgewogICAgICAgICAgICBhd2FpdCBsb2coImVycm9yIiwgInB1c2g6IGZhbGxiYWNr\
|
||||
IHNob3dOb3RpZmljYXRpb24gYWxzbyBmYWlsZWQiLCB7CiAgICAgICAgICAgICAgZXJyb3I6IGRlc2NyaWJlRXJyb3IoZmFsbGJh\
|
||||
Y2tFcnJvcikKICAgICAgICAgICAgfSk7CiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9KSgpCiAgICApOwogIH0pOwogIHNl\
|
||||
bGYuYWRkRXZlbnRMaXN0ZW5lcigibm90aWZpY2F0aW9uY2xpY2siLCAoZXZlbnQpID0+IHsKICAgIGV2ZW50Lm5vdGlmaWNhdGlv\
|
||||
bi5jbG9zZSgpOwogICAgY29uc3QgdGFyZ2V0VXJsID0gcmVzb2x2ZVRhcmdldFVybChldmVudC5ub3RpZmljYXRpb24uZGF0YT8u\
|
||||
dXJsKTsKICAgIGNvbnN0IHRhcmdldFVzZXJJZCA9IGV2ZW50Lm5vdGlmaWNhdGlvbi5kYXRhPy50YXJnZXRfdXNlcl9pZDsKICAg\
|
||||
IGV2ZW50LndhaXRVbnRpbCgKICAgICAgKGFzeW5jICgpID0+IHsKICAgICAgICBhd2FpdCBlbnN1cmVMb2dnaW5nRW5hYmxlZExv\
|
||||
YWRlZDsKICAgICAgICBhd2FpdCBsb2coImluZm8iLCAibm90aWZpY2F0aW9uY2xpY2siLCB7IHRhcmdldFVybCwgdGFyZ2V0VXNl\
|
||||
cklkLCB0YWc6IGV2ZW50Lm5vdGlmaWNhdGlvbi50YWcgfSk7CiAgICAgICAgaWYgKCF0YXJnZXRVcmwpIHJldHVybjsKICAgICAg\
|
||||
ICB0cnkgewogICAgICAgICAgYXdhaXQgZm9jdXNPck9wZW5DbGllbnQodGFyZ2V0VXJsLCB0YXJnZXRVc2VySWQpOwogICAgICAg\
|
||||
IH0gY2F0Y2ggKGVycm9yKSB7CiAgICAgICAgICBhd2FpdCBsb2coImVycm9yIiwgIm5vdGlmaWNhdGlvbmNsaWNrOiBmb2N1c09y\
|
||||
T3BlbiBmYWlsZWQiLCB7IGVycm9yOiBkZXNjcmliZUVycm9yKGVycm9yKSB9KTsKICAgICAgICB9CiAgICAgIH0pKCkKICAgICk7\
|
||||
CiAgfSk7CiAgc2VsZi5hZGRFdmVudExpc3RlbmVyKCJub3RpZmljYXRpb25jbG9zZSIsIChldmVudCkgPT4gewogICAgZXZlbnQu\
|
||||
d2FpdFVudGlsKGxvZygiZGVidWciLCAibm90aWZpY2F0aW9uY2xvc2UiLCB7IHRhZzogZXZlbnQubm90aWZpY2F0aW9uLnRhZyB9\
|
||||
KSk7CiAgfSk7CiAgdmFyIGFycmF5QnVmZmVyVG9CYXNlNjRVcmwgPSAoYnVmZmVyKSA9PiB7CiAgICBpZiAoIWJ1ZmZlcikgcmV0\
|
||||
dXJuIG51bGw7CiAgICBjb25zdCBieXRlcyA9IG5ldyBVaW50OEFycmF5KGJ1ZmZlcik7CiAgICBsZXQgYmluYXJ5ID0gIiI7CiAg\
|
||||
ICBmb3IgKGxldCBpID0gMDsgaSA8IGJ5dGVzLmxlbmd0aDsgaSArPSAxKSBiaW5hcnkgKz0gU3RyaW5nLmZyb21DaGFyQ29kZShi\
|
||||
eXRlc1tpXSk7CiAgICByZXR1cm4gYnRvYShiaW5hcnkpLnJlcGxhY2UoL1wrL2csICItIikucmVwbGFjZSgvXC8vZywgIl8iKS5y\
|
||||
ZXBsYWNlKC89KyQvLCAiIik7CiAgfTsKICB2YXIgdXJsQmFzZTY0VG9VaW50OEFycmF5ID0gKGJhc2U2NCkgPT4gewogICAgY29u\
|
||||
c3QgcGFkZGluZyA9ICI9Ii5yZXBlYXQoKDQgLSBiYXNlNjQubGVuZ3RoICUgNCkgJSA0KTsKICAgIGNvbnN0IHJhdyA9IGF0b2Io\
|
||||
KGJhc2U2NCArIHBhZGRpbmcpLnJlcGxhY2UoLy0vZywgIisiKS5yZXBsYWNlKC9fL2csICIvIikpOwogICAgY29uc3Qgb3V0ID0g\
|
||||
bmV3IFVpbnQ4QXJyYXkocmF3Lmxlbmd0aCk7CiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHJhdy5sZW5ndGg7IGkgKz0gMSkgb3V0\
|
||||
W2ldID0gcmF3LmNoYXJDb2RlQXQoaSk7CiAgICByZXR1cm4gb3V0OwogIH07CiAgdmFyIGZldGNoSW5zdGFuY2VDb25maWcgPSBh\
|
||||
c3luYyAoKSA9PiB7CiAgICB0cnkgewogICAgICBjb25zdCByZXMgPSBhd2FpdCBmZXRjaCgiLy53ZWxsLWtub3duL2ZsdXhlciIs\
|
||||
IHsgY3JlZGVudGlhbHM6ICJpbmNsdWRlIiB9KTsKICAgICAgaWYgKCFyZXMub2spIHJldHVybiBudWxsOwogICAgICBjb25zdCBk\
|
||||
YXRhID0gYXdhaXQgcmVzLmpzb24oKTsKICAgICAgY29uc3QgYXBpQ2xpZW50ID0gZGF0YS5lbmRwb2ludHM/LmFwaV9jbGllbnQg\
|
||||
Pz8gZGF0YS5lbmRwb2ludHM/LmFwaTsKICAgICAgaWYgKCFhcGlDbGllbnQpIHJldHVybiBudWxsOwogICAgICByZXR1cm4geyBh\
|
||||
cGlDbGllbnQsIHZhcGlkS2V5OiBkYXRhLnB1c2g/LnB1YmxpY192YXBpZF9rZXkgPz8gbnVsbCB9OwogICAgfSBjYXRjaCAoZXJy\
|
||||
b3IpIHsKICAgICAgYXdhaXQgbG9nKCJlcnJvciIsICJGYWlsZWQgdG8gZmV0Y2ggaW5zdGFuY2UgY29uZmlnIGluIFNXIiwgeyBl\
|
||||
cnJvcjogZGVzY3JpYmVFcnJvcihlcnJvcikgfSk7CiAgICAgIHJldHVybiBudWxsOwogICAgfQogIH07CiAgdmFyIFNXX0FQSV9W\
|
||||
RVJTSU9OID0gMTsKICB2YXIgcm90YXRlU3Vic2NyaXB0aW9uT25TZXJ2ZXIgPSBhc3luYyAob2xkRW5kcG9pbnQsIG5ld1N1Yiwg\
|
||||
YXBpQ2xpZW50KSA9PiB7CiAgICBjb25zdCBwMjU2ZGggPSBhcnJheUJ1ZmZlclRvQmFzZTY0VXJsKG5ld1N1Yi5nZXRLZXkoInAy\
|
||||
NTZkaCIpKTsKICAgIGNvbnN0IGF1dGggPSBhcnJheUJ1ZmZlclRvQmFzZTY0VXJsKG5ld1N1Yi5nZXRLZXkoImF1dGgiKSk7CiAg\
|
||||
ICBpZiAoIXAyNTZkaCB8fCAhYXV0aCkgewogICAgICBhd2FpdCBsb2coImVycm9yIiwgInJvdGF0ZTogbmV3IHN1YnNjcmlwdGlv\
|
||||
biBtaXNzaW5nIGtleXMiKTsKICAgICAgcmV0dXJuOwogICAgfQogICAgY29uc3QgdXJsID0gYCR7YXBpQ2xpZW50fS92JHtTV19B\
|
||||
UElfVkVSU0lPTn0vdXNlcnMvQG1lL3B1c2gvcm90YXRlYDsKICAgIHRyeSB7CiAgICAgIGNvbnN0IHJlcyA9IGF3YWl0IGZldGNo\
|
||||
KHVybCwgewogICAgICAgIG1ldGhvZDogIlBPU1QiLAogICAgICAgIGNyZWRlbnRpYWxzOiAiaW5jbHVkZSIsCiAgICAgICAgaGVh\
|
||||
ZGVyczogeyAiQ29udGVudC1UeXBlIjogImFwcGxpY2F0aW9uL2pzb24iIH0sCiAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnko\
|
||||
ewogICAgICAgICAgb2xkX2VuZHBvaW50OiBvbGRFbmRwb2ludCwKICAgICAgICAgIGVuZHBvaW50OiBuZXdTdWIuZW5kcG9pbnQs\
|
||||
CiAgICAgICAgICBrZXlzOiB7IHAyNTZkaCwgYXV0aCB9LAogICAgICAgICAgdXNlcl9hZ2VudDogc2VsZi5uYXZpZ2F0b3IudXNl\
|
||||
ckFnZW50CiAgICAgICAgfSkKICAgICAgfSk7CiAgICAgIGF3YWl0IGxvZyhyZXMub2sgPyAiaW5mbyIgOiAiZXJyb3IiLCAicm90\
|
||||
YXRlOiBzZXJ2ZXIgcmVzcG9uc2UiLCB7IHN0YXR1czogcmVzLnN0YXR1cywgb2s6IHJlcy5vayB9KTsKICAgIH0gY2F0Y2ggKGVy\
|
||||
cm9yKSB7CiAgICAgIGF3YWl0IGxvZygiZXJyb3IiLCAicm90YXRlOiBmZXRjaCBmYWlsZWQiLCB7IGVycm9yOiBkZXNjcmliZUVy\
|
||||
cm9yKGVycm9yKSB9KTsKICAgIH0KICB9OwogIHNlbGYuYWRkRXZlbnRMaXN0ZW5lcigicHVzaHN1YnNjcmlwdGlvbmNoYW5nZSIs\
|
||||
IChldmVudCkgPT4gewogICAgZXZlbnQud2FpdFVudGlsKAogICAgICAoYXN5bmMgKCkgPT4gewogICAgICAgIGF3YWl0IGVuc3Vy\
|
||||
ZUxvZ2dpbmdFbmFibGVkTG9hZGVkOwogICAgICAgIGF3YWl0IGxvZygiaW5mbyIsICJwdXNoc3Vic2NyaXB0aW9uY2hhbmdlIiwg\
|
||||
ewogICAgICAgICAgaGFzT2xkOiBCb29sZWFuKGV2ZW50Lm9sZFN1YnNjcmlwdGlvbiksCiAgICAgICAgICBoYXNOZXc6IEJvb2xl\
|
||||
YW4oZXZlbnQubmV3U3Vic2NyaXB0aW9uKSwKICAgICAgICAgIG9sZEVuZHBvaW50OiBldmVudC5vbGRTdWJzY3JpcHRpb24/LmVu\
|
||||
ZHBvaW50ID8/IG51bGwsCiAgICAgICAgICBuZXdFbmRwb2ludDogZXZlbnQubmV3U3Vic2NyaXB0aW9uPy5lbmRwb2ludCA/PyBu\
|
||||
dWxsCiAgICAgICAgfSk7CiAgICAgICAgdHJ5IHsKICAgICAgICAgIGNvbnN0IG9sZEVuZHBvaW50ID0gZXZlbnQub2xkU3Vic2Ny\
|
||||
aXB0aW9uPy5lbmRwb2ludCA/PyBudWxsOwogICAgICAgICAgbGV0IGFwcFNlcnZlcktleSA9IGV2ZW50Lm9sZFN1YnNjcmlwdGlv\
|
||||
bj8ub3B0aW9ucz8uYXBwbGljYXRpb25TZXJ2ZXJLZXkgPz8gbnVsbDsKICAgICAgICAgIGNvbnN0IGNvbmZpZyA9IGF3YWl0IGZl\
|
||||
dGNoSW5zdGFuY2VDb25maWcoKTsKICAgICAgICAgIGlmICghYXBwU2VydmVyS2V5ICYmIGNvbmZpZz8udmFwaWRLZXkpIHsKICAg\
|
||||
ICAgICAgICAgYXBwU2VydmVyS2V5ID0gdXJsQmFzZTY0VG9VaW50OEFycmF5KGNvbmZpZy52YXBpZEtleSkuYnVmZmVyOwogICAg\
|
||||
ICAgICAgfQogICAgICAgICAgaWYgKCFhcHBTZXJ2ZXJLZXkpIHsKICAgICAgICAgICAgYXdhaXQgbG9nKCJlcnJvciIsICJwdXNo\
|
||||
c3Vic2NyaXB0aW9uY2hhbmdlOiBubyBWQVBJRCBrZXkgYXZhaWxhYmxlIik7CiAgICAgICAgICAgIGF3YWl0IHBvc3RNZXNzYWdl\
|
||||
VG9DbGllbnRzKHsgdHlwZTogIlBVU0hfU1VCU0NSSVBUSU9OX0NIQU5HRSIgfSk7CiAgICAgICAgICAgIHJldHVybjsKICAgICAg\
|
||||
ICAgIH0KICAgICAgICAgIGNvbnN0IG5ld1N1YiA9IGV2ZW50Lm5ld1N1YnNjcmlwdGlvbiA/PyBhd2FpdCBzZWxmLnJlZ2lzdHJh\
|
||||
dGlvbi5wdXNoTWFuYWdlci5zdWJzY3JpYmUoewogICAgICAgICAgICB1c2VyVmlzaWJsZU9ubHk6IHRydWUsCiAgICAgICAgICAg\
|
||||
IGFwcGxpY2F0aW9uU2VydmVyS2V5OiBhcHBTZXJ2ZXJLZXkKICAgICAgICAgIH0pOwogICAgICAgICAgaWYgKG9sZEVuZHBvaW50\
|
||||
ICYmIGNvbmZpZykgewogICAgICAgICAgICBhd2FpdCByb3RhdGVTdWJzY3JpcHRpb25PblNlcnZlcihvbGRFbmRwb2ludCwgbmV3\
|
||||
U3ViLCBjb25maWcuYXBpQ2xpZW50KTsKICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGF3YWl0IGxvZygid2FybiIsICJw\
|
||||
dXNoc3Vic2NyaXB0aW9uY2hhbmdlOiB1bmFibGUgdG8gcm90YXRlIG9uIHNlcnZlciIsIHsKICAgICAgICAgICAgICBoYXNPbGRF\
|
||||
bmRwb2ludDogQm9vbGVhbihvbGRFbmRwb2ludCksCiAgICAgICAgICAgICAgaGFzQ29uZmlnOiBCb29sZWFuKGNvbmZpZykKICAg\
|
||||
ICAgICAgICAgfSk7CiAgICAgICAgICB9CiAgICAgICAgICBhd2FpdCBwb3N0TWVzc2FnZVRvQ2xpZW50cyh7IHR5cGU6ICJQVVNI\
|
||||
X1NVQlNDUklQVElPTl9DSEFOR0UiIH0pOwogICAgICAgIH0gY2F0Y2ggKGVycm9yKSB7CiAgICAgICAgICBhd2FpdCBsb2coImVy\
|
||||
cm9yIiwgInB1c2hzdWJzY3JpcHRpb25jaGFuZ2UgaGFuZGxlciBmYWlsZWQiLCB7IGVycm9yOiBkZXNjcmliZUVycm9yKGVycm9y\
|
||||
KSB9KTsKICAgICAgICAgIGF3YWl0IHBvc3RNZXNzYWdlVG9DbGllbnRzKHsgdHlwZTogIlBVU0hfU1VCU0NSSVBUSU9OX0NIQU5H\
|
||||
RSIgfSkuY2F0Y2goKCkgPT4gdm9pZCAwKTsKICAgICAgICB9CiAgICAgIH0pKCkKICAgICk7CiAgfSk7Cn0pKCk7Ci8vIyBzb3Vy\
|
||||
Y2VNYXBwaW5nVVJMPXN3LmpzLm1hcAo=\
|
||||
";
|
||||
const STABLE_VERSION_JSON: &str = "\
|
||||
eyJzaGEiOiIzMjUwYzczY2JmOTRlOTdlOGI5Y2E1YTBlY2IwM2MyMGJkZGUzODY3IiwiYnVpbGROdW1iZXIiOjUwOCwiYnVpbGRU\
|
||||
aW1lc3RhbXAiOiIxNzgwNzgxMTY4In0=\
|
||||
";
|
||||
|
||||
fn decode(b64: &str) -> Vec<u8> {
|
||||
base64::engine::general_purpose::STANDARD
|
||||
.decode(b64)
|
||||
.expect("frozen snapshot base64 decode failed")
|
||||
}
|
||||
|
||||
pub static STABLE_SNAPSHOT: LazyLock<FrozenSnapshot> = LazyLock::new(|| FrozenSnapshot {
|
||||
sha: STABLE_SNAPSHOT_SHA.to_owned(),
|
||||
index_html: decode(STABLE_INDEX_HTML),
|
||||
sw_js: decode(STABLE_SW_JS),
|
||||
version_json: decode(STABLE_VERSION_JSON),
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn stable_snapshot_sha_matches() {
|
||||
assert_eq!(STABLE_SNAPSHOT.sha, STABLE_SNAPSHOT_SHA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_html_decodes_and_starts_with_doctype() {
|
||||
assert_index_html(&STABLE_SNAPSHOT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sw_js_decodes_and_is_javascript() {
|
||||
assert_sw_js(&STABLE_SNAPSHOT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_json_decodes_and_is_valid_json() {
|
||||
assert_version_json(&STABLE_SNAPSHOT);
|
||||
}
|
||||
|
||||
fn assert_index_html(snapshot: &FrozenSnapshot) {
|
||||
let html = std::str::from_utf8(&snapshot.index_html).unwrap();
|
||||
assert!(
|
||||
html.starts_with("<!doctype html>"),
|
||||
"unexpected start: {}",
|
||||
&html[..40]
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_sw_js(snapshot: &FrozenSnapshot) {
|
||||
let js = std::str::from_utf8(&snapshot.sw_js).unwrap();
|
||||
assert!(
|
||||
js.contains("use strict"),
|
||||
"sw.js should contain 'use strict'"
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_version_json(snapshot: &FrozenSnapshot) {
|
||||
let json_str = std::str::from_utf8(&snapshot.version_json).unwrap();
|
||||
let parsed: serde_json::Value = serde_json::from_str(json_str).unwrap();
|
||||
assert!(
|
||||
parsed
|
||||
.get("sha")
|
||||
.or_else(|| parsed.get("version"))
|
||||
.is_some(),
|
||||
"version.json should have a sha or version field"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,6 @@ pub mod bootstrap;
|
||||
pub mod config;
|
||||
pub mod csp;
|
||||
pub mod discovery_cache;
|
||||
#[cfg(feature = "time-freeze")]
|
||||
pub mod frozen_snapshots;
|
||||
pub mod geoip;
|
||||
pub mod routes;
|
||||
pub mod state;
|
||||
pub mod time_freeze;
|
||||
|
||||
@@ -9,9 +9,6 @@ use crate::state::{
|
||||
AppProxyBudgets, AppState, MAX_RENDERED_SPA_INDEX_BYTES, MAX_SPA_INDEX_BYTES,
|
||||
read_bounded_text_file,
|
||||
};
|
||||
use crate::time_freeze::{
|
||||
load_time_freeze_config_for_request, should_serve_frozen, time_freeze_debug_header,
|
||||
};
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
extract::{Request, State},
|
||||
@@ -142,8 +139,6 @@ async fn serve_static_file(
|
||||
}
|
||||
|
||||
async fn serve_spa_index(state: &AppState, headers: &HeaderMap) -> Response {
|
||||
let time_freeze = load_time_freeze_config_for_request(&state.config, headers);
|
||||
let debug_header = time_freeze_debug_header(&time_freeze);
|
||||
let should_bust_dev_assets = state.config.index_upstream_url.is_some();
|
||||
|
||||
let discovery = match refresh_discovery_for_spa(state).await {
|
||||
@@ -168,13 +163,9 @@ async fn serve_spa_index(state: &AppState, headers: &HeaderMap) -> Response {
|
||||
let geoip = build_geoip_response(state.geoip.lookup(headers));
|
||||
let script_tag = build_bootstrap_script(&state.config, &discovery, &geoip, &nonce);
|
||||
|
||||
let raw_html = if let Some(snapshot) = should_serve_frozen(&time_freeze) {
|
||||
String::from_utf8_lossy(&snapshot.index_html).into_owned()
|
||||
} else {
|
||||
match load_spa_index_html(state).await {
|
||||
let raw_html = match load_spa_index_html(state).await {
|
||||
Ok(content) => content,
|
||||
Err(response) => return response,
|
||||
}
|
||||
};
|
||||
|
||||
let dev_buster = should_bust_dev_assets.then(current_dev_asset_cache_buster);
|
||||
@@ -193,7 +184,7 @@ async fn serve_spa_index(state: &AppState, headers: &HeaderMap) -> Response {
|
||||
}
|
||||
};
|
||||
let html = html.into_boxed_str();
|
||||
build_spa_response(html, csp, debug_header.as_deref(), should_bust_dev_assets)
|
||||
build_spa_response(html, csp, should_bust_dev_assets)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -377,12 +368,7 @@ impl AsRef<[u8]> for SpaDocumentBody {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_spa_response(
|
||||
html: Box<str>,
|
||||
csp: HeaderValue,
|
||||
time_freeze_header: Option<&str>,
|
||||
dev_no_store: bool,
|
||||
) -> Response {
|
||||
fn build_spa_response(html: Box<str>, csp: HeaderValue, dev_no_store: bool) -> Response {
|
||||
let body = Bytes::from_owner(SpaDocumentBody { html });
|
||||
let mut response = Response::new(Body::from(body));
|
||||
let headers = response.headers_mut();
|
||||
@@ -419,17 +405,6 @@ fn build_spa_response(
|
||||
axum::http::HeaderName::from_static("critical-ch"),
|
||||
HeaderValue::from_static(CRITICAL_CH_VALUE),
|
||||
);
|
||||
#[cfg(feature = "time-freeze")]
|
||||
{
|
||||
if let Some(tf) = time_freeze_header
|
||||
&& let Ok(v) = HeaderValue::from_str(tf)
|
||||
{
|
||||
headers.insert(axum::http::HeaderName::from_static("x-time-freeze"), v);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "time-freeze"))]
|
||||
let _ = time_freeze_header;
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
@@ -667,40 +642,6 @@ mod tests {
|
||||
assert!(!untouched.contains("_=9911"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "time-freeze")]
|
||||
#[test]
|
||||
fn the_frozen_shell_is_never_served_with_an_unfilled_nonce_or_a_missing_bootstrap() {
|
||||
let frozen = String::from_utf8_lossy(&crate::frozen_snapshots::STABLE_SNAPSHOT.index_html)
|
||||
.into_owned();
|
||||
assert!(
|
||||
frozen.contains("{{CSP_NONCE_PLACEHOLDER}}"),
|
||||
"the captured shell should still carry the hole this test proves we fill"
|
||||
);
|
||||
|
||||
let served = render_spa_document(
|
||||
&frozen,
|
||||
"frozennonce",
|
||||
"<script>frozenboot</script>",
|
||||
"https://fluxerstatic.com",
|
||||
"",
|
||||
None,
|
||||
)
|
||||
.expect("test SPA document must render within its size limit");
|
||||
|
||||
assert!(
|
||||
!served.contains("{{CSP_NONCE_PLACEHOLDER}}"),
|
||||
"a frozen-served shell shipped a literal nonce placeholder"
|
||||
);
|
||||
assert!(
|
||||
served.contains(r#"nonce="frozennonce""#),
|
||||
"a frozen-served shell lost its per-request nonce"
|
||||
);
|
||||
assert!(
|
||||
served.contains("<script>frozenboot</script>"),
|
||||
"a frozen-served shell shipped without the bootstrap script"
|
||||
);
|
||||
}
|
||||
|
||||
const SHELL_WITH_ENDPOINT_HOLES: &str = r#"<!doctype html><html><head><title>Fluxer</title><link rel="preconnect" href="{{STATIC_CDN_ENDPOINT}}">
|
||||
<link rel="preconnect" href="{{STATIC_CDN_ENDPOINT}}" crossorigin>
|
||||
<link rel="preconnect" href="{{MEDIA_ENDPOINT}}">
|
||||
@@ -840,7 +781,6 @@ mod tests {
|
||||
let discovery_upstream_url = spawn_local_origin(discovery_body, "application/json").await;
|
||||
let mut config = AppProxyConfig::from_env();
|
||||
config.release_channel = channel;
|
||||
config.time_freeze_enabled = true;
|
||||
config.index_upstream_url = index_upstream_url.map(|url| {
|
||||
crate::config::HttpUrl::parse("TEST_INDEX_UPSTREAM_URL", &url)
|
||||
.expect("test index upstream URL must be a valid HTTP URL")
|
||||
@@ -948,158 +888,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "time-freeze")]
|
||||
#[tokio::test]
|
||||
async fn the_frozen_document_is_served_with_its_asset_urls_untouched() {
|
||||
let captured =
|
||||
String::from_utf8_lossy(&crate::frozen_snapshots::STABLE_SNAPSHOT.index_html)
|
||||
.into_owned();
|
||||
assert!(
|
||||
captured.contains(
|
||||
r#"<link rel="stylesheet" href="https://fluxerstatic.com/fonts/ibm-plex.css">"#
|
||||
),
|
||||
"the captured shell no longer carries the asset URL this test proves we leave alone"
|
||||
);
|
||||
|
||||
let state = spa_state_serving(ReleaseChannel::Stable, None).await;
|
||||
|
||||
let response = serve_spa_index(&state, &HeaderMap::new()).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let served = read_document(response).await;
|
||||
|
||||
assert!(
|
||||
served.contains(
|
||||
r#"<link rel="stylesheet" href="https://fluxerstatic.com/fonts/ibm-plex.css">"#
|
||||
),
|
||||
"the frozen document's stylesheet URL was rewritten on the primary hosted path"
|
||||
);
|
||||
assert!(
|
||||
served.contains(r#"<link rel="manifest" href="/manifest.json">"#),
|
||||
"the frozen document's manifest URL was rewritten on the primary hosted path"
|
||||
);
|
||||
assert!(
|
||||
!served.contains("?_=") && !served.contains("&_="),
|
||||
"the frozen document was served with a development cache-busting query"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "time-freeze")]
|
||||
#[tokio::test]
|
||||
async fn the_frozen_branch_serves_a_rendered_document_and_not_the_raw_shell() {
|
||||
let captured =
|
||||
String::from_utf8_lossy(&crate::frozen_snapshots::STABLE_SNAPSHOT.index_html)
|
||||
.into_owned();
|
||||
assert!(
|
||||
captured.contains("{{CSP_NONCE_PLACEHOLDER}}"),
|
||||
"the captured shell no longer carries the nonce hole this test proves we fill"
|
||||
);
|
||||
assert!(
|
||||
!captured.contains("window.__FLUXER_BOOTSTRAP__"),
|
||||
"the captured shell already carries a bootstrap, so this test can no longer tell a rendered document from a raw shell"
|
||||
);
|
||||
|
||||
let state = spa_state_serving(ReleaseChannel::Stable, None).await;
|
||||
|
||||
let response = serve_spa_index(&state, &HeaderMap::new()).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let granted_nonce = nonce_granted_by(&response);
|
||||
let served = read_document(response).await;
|
||||
|
||||
assert!(
|
||||
!served.contains("{{CSP_NONCE_PLACEHOLDER}}"),
|
||||
"the primary hosted path shipped a literal nonce placeholder"
|
||||
);
|
||||
assert_eq!(
|
||||
served
|
||||
.matches(&format!(r#"nonce="{granted_nonce}""#))
|
||||
.count(),
|
||||
served.matches(r#"nonce=""#).count(),
|
||||
"the frozen document carries a nonce its own policy header never granted"
|
||||
);
|
||||
assert!(
|
||||
served.contains(&format!(
|
||||
r#"<script nonce="{granted_nonce}">window.__FLUXER_BOOTSTRAP__"#
|
||||
)),
|
||||
"the primary hosted path shipped without a bootstrap script the browser will run"
|
||||
);
|
||||
|
||||
let second = serve_spa_index(&state, &HeaderMap::new()).await;
|
||||
assert_ne!(
|
||||
nonce_granted_by(&second),
|
||||
granted_nonce,
|
||||
"two frozen requests were granted the same nonce"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "time-freeze")]
|
||||
#[tokio::test]
|
||||
async fn every_branch_announces_which_snapshot_decision_it_took() {
|
||||
let frozen_state = spa_state_serving(ReleaseChannel::Stable, None).await;
|
||||
let frozen = serve_spa_index(&frozen_state, &HeaderMap::new()).await;
|
||||
assert_eq!(
|
||||
frozen
|
||||
.headers()
|
||||
.get("x-time-freeze")
|
||||
.expect("the frozen response announced no snapshot decision")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
format!(
|
||||
"frozen; sha={}",
|
||||
crate::frozen_snapshots::STABLE_SNAPSHOT.sha
|
||||
),
|
||||
"the frozen response named the wrong snapshot"
|
||||
);
|
||||
|
||||
let live_state =
|
||||
spa_state_serving(ReleaseChannel::Canary, Some(SHELL_WITH_ENDPOINT_HOLES)).await;
|
||||
let live = serve_spa_index(&live_state, &HeaderMap::new()).await;
|
||||
assert_eq!(
|
||||
live.headers()
|
||||
.get("x-time-freeze")
|
||||
.expect("the live response announced no snapshot decision")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"no-snapshot",
|
||||
"a channel with no snapshot claimed one anyway"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "time-freeze")]
|
||||
#[tokio::test]
|
||||
async fn the_frozen_shell_is_never_served_with_the_asset_lifetime() {
|
||||
let state = spa_state_serving(ReleaseChannel::Stable, None).await;
|
||||
|
||||
let response = serve_spa_index(&state, &HeaderMap::new()).await;
|
||||
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get("x-time-freeze")
|
||||
.expect("the response announced no snapshot decision")
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
format!(
|
||||
"frozen; sha={}",
|
||||
crate::frozen_snapshots::STABLE_SNAPSHOT.sha
|
||||
),
|
||||
"this test never reached the frozen branch, so it proves nothing about it"
|
||||
);
|
||||
let cache_control = response
|
||||
.headers()
|
||||
.get(header::CACHE_CONTROL)
|
||||
.expect("the frozen shell was served without a cache policy at all")
|
||||
.to_str()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
cache_control, "no-cache",
|
||||
"the frozen document naming the hashed bundle must be revalidated on every load"
|
||||
);
|
||||
assert_ne!(
|
||||
cache_control, LONG_LIVED_ASSET_CACHE_CONTROL,
|
||||
"a frozen shell cached for a year pins every returning visitor to the deployed-over bundle"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_crawl_control_document_is_never_served_with_the_asset_lifetime() {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
|
||||
@@ -3,54 +3,14 @@
|
||||
use crate::config::HttpEndpoint;
|
||||
use crate::discovery_cache::discovery_endpoint;
|
||||
use crate::state::{AppState, MAX_STATIC_TEXT_FILE_BYTES, read_bounded_file};
|
||||
use crate::time_freeze::{
|
||||
TimeFreezeConfig, describe_decision, load_time_freeze_config_for_request,
|
||||
time_freeze_debug_header,
|
||||
};
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::{HeaderMap, HeaderValue, StatusCode, header},
|
||||
http::{HeaderValue, StatusCode, header},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
fn serve_frozen_file(
|
||||
config: &TimeFreezeConfig,
|
||||
pick: impl FnOnce(&crate::time_freeze::FrozenSnapshot) -> Option<(&[u8], &str)>,
|
||||
) -> (Option<Response>, Option<String>) {
|
||||
let debug = describe_decision(config);
|
||||
let debug_header = time_freeze_debug_header(config);
|
||||
|
||||
if debug.decision != crate::time_freeze::TimeFreezeDecision::Frozen {
|
||||
return (None, debug_header);
|
||||
}
|
||||
|
||||
if let Some(snapshot) = config.snapshot
|
||||
&& let Some((bytes, content_type)) = pick(snapshot)
|
||||
{
|
||||
let mut response = bytes.to_vec().into_response();
|
||||
if let Ok(ct) = HeaderValue::from_str(content_type) {
|
||||
response.headers_mut().insert(header::CONTENT_TYPE, ct);
|
||||
}
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache"));
|
||||
set_time_freeze_header(&mut response, debug_header.as_deref());
|
||||
return (Some(response), None);
|
||||
}
|
||||
|
||||
(None, debug_header)
|
||||
}
|
||||
|
||||
pub async fn version_json(State(state): State<AppState>, headers: HeaderMap) -> Response {
|
||||
let tf = load_time_freeze_config_for_request(&state.config, &headers);
|
||||
let (frozen, debug_header) = serve_frozen_file(&tf, |snap| {
|
||||
Some((snap.version_json.as_slice(), "application/json"))
|
||||
});
|
||||
if let Some(resp) = frozen {
|
||||
return resp;
|
||||
}
|
||||
|
||||
pub async fn version_json(State(state): State<AppState>) -> Response {
|
||||
let mut result = serve_static_text_file(&state, "version.json", "application/json").await;
|
||||
|
||||
if result.status() == StatusCode::NOT_FOUND && !state.config.build_version.is_empty() {
|
||||
@@ -61,7 +21,6 @@ pub async fn version_json(State(state): State<AppState>, headers: HeaderMap) ->
|
||||
.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache"));
|
||||
}
|
||||
|
||||
set_time_freeze_header(&mut result, debug_header.as_deref());
|
||||
result
|
||||
}
|
||||
|
||||
@@ -87,43 +46,14 @@ pub async fn browserconfig_xml(State(state): State<AppState>) -> Response {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn service_worker(State(state): State<AppState>, headers: HeaderMap) -> Response {
|
||||
let tf = load_time_freeze_config_for_request(&state.config, &headers);
|
||||
let (frozen, debug_header) = serve_frozen_file(&tf, |snap| {
|
||||
Some((
|
||||
snap.sw_js.as_slice(),
|
||||
"application/javascript; charset=utf-8",
|
||||
))
|
||||
});
|
||||
if let Some(resp) = frozen {
|
||||
return resp;
|
||||
}
|
||||
let mut result =
|
||||
serve_static_text_file(&state, "sw.js", "application/javascript; charset=utf-8").await;
|
||||
set_time_freeze_header(&mut result, debug_header.as_deref());
|
||||
result
|
||||
pub async fn service_worker(State(state): State<AppState>) -> Response {
|
||||
serve_static_text_file(&state, "sw.js", "application/javascript; charset=utf-8").await
|
||||
}
|
||||
|
||||
pub async fn service_worker_map(State(state): State<AppState>) -> Response {
|
||||
serve_static_text_file(&state, "sw.js.map", "application/json").await
|
||||
}
|
||||
|
||||
fn set_time_freeze_header(response: &mut Response, value: Option<&str>) {
|
||||
#[cfg(feature = "time-freeze")]
|
||||
{
|
||||
if let Some(v) = value
|
||||
&& let Ok(hv) = HeaderValue::from_str(v)
|
||||
{
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(axum::http::HeaderName::from_static("x-time-freeze"), hv);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "time-freeze"))]
|
||||
let _ = (response, value);
|
||||
}
|
||||
|
||||
async fn runtime_static_cdn_endpoint(state: &AppState) -> Option<HttpEndpoint> {
|
||||
if let Some(discovery) = state.discovery_cache.get().await
|
||||
&& let Some(endpoint) = discovery_endpoint(&discovery, "static_cdn")
|
||||
|
||||
@@ -1,498 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::config::{AppProxyConfig, ReleaseChannel};
|
||||
use axum::http::{HeaderMap, header};
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
const TIME_FREEZE_EXEMPT_CLIENT_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(188, 149, 230, 148));
|
||||
const CANARY_USER_AGENT_PRODUCT: &str = "FluxerCanary/";
|
||||
const CANARY_LIVE_WEB_CLIENT_MIN_VERSION: CalVer = CalVer {
|
||||
year: 2026,
|
||||
month_day: 614,
|
||||
micro: 0,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
struct CalVer {
|
||||
year: u16,
|
||||
month_day: u16,
|
||||
micro: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FrozenSnapshot {
|
||||
pub sha: String,
|
||||
pub index_html: Vec<u8>,
|
||||
pub sw_js: Vec<u8>,
|
||||
pub version_json: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TimeFreezeConfig {
|
||||
pub enabled: bool,
|
||||
pub release_channel: ReleaseChannel,
|
||||
pub snapshot: Option<&'static FrozenSnapshot>,
|
||||
pub client_exempt: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TimeFreezeDebug {
|
||||
pub decision: TimeFreezeDecision,
|
||||
pub snapshot_sha: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum TimeFreezeDecision {
|
||||
Frozen,
|
||||
Live,
|
||||
NoSnapshot,
|
||||
}
|
||||
|
||||
pub fn load_time_freeze_config_for_request(
|
||||
app_config: &AppProxyConfig,
|
||||
headers: &HeaderMap,
|
||||
) -> TimeFreezeConfig {
|
||||
let client_exempt = is_time_freeze_exempt_client(
|
||||
headers,
|
||||
app_config.trust_client_ip_header,
|
||||
&app_config.client_ip_header_name,
|
||||
);
|
||||
load_time_freeze_config_for_index_source(
|
||||
app_config.release_channel,
|
||||
client_exempt,
|
||||
app_config.index_upstream_url.is_some(),
|
||||
app_config.time_freeze_enabled,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn load_time_freeze_config(
|
||||
release_channel: ReleaseChannel,
|
||||
client_exempt: bool,
|
||||
) -> TimeFreezeConfig {
|
||||
load_time_freeze_config_for_index_source(release_channel, client_exempt, false, true)
|
||||
}
|
||||
|
||||
pub fn load_time_freeze_config_for_index_source(
|
||||
release_channel: ReleaseChannel,
|
||||
client_exempt: bool,
|
||||
has_index_upstream: bool,
|
||||
enabled: bool,
|
||||
) -> TimeFreezeConfig {
|
||||
if !enabled || has_index_upstream {
|
||||
return TimeFreezeConfig {
|
||||
enabled,
|
||||
release_channel,
|
||||
snapshot: None,
|
||||
client_exempt,
|
||||
};
|
||||
}
|
||||
|
||||
let snapshot: Option<&'static FrozenSnapshot> = load_frozen_snapshot(release_channel);
|
||||
|
||||
TimeFreezeConfig {
|
||||
enabled,
|
||||
release_channel,
|
||||
snapshot,
|
||||
client_exempt,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "time-freeze")]
|
||||
fn load_frozen_snapshot(release_channel: ReleaseChannel) -> Option<&'static FrozenSnapshot> {
|
||||
match release_channel {
|
||||
ReleaseChannel::Stable => Some(&crate::frozen_snapshots::STABLE_SNAPSHOT),
|
||||
ReleaseChannel::Canary => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "time-freeze"))]
|
||||
fn load_frozen_snapshot(_release_channel: ReleaseChannel) -> Option<&'static FrozenSnapshot> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn should_serve_frozen(config: &TimeFreezeConfig) -> Option<&'static FrozenSnapshot> {
|
||||
if !config.enabled {
|
||||
return None;
|
||||
}
|
||||
if config.client_exempt {
|
||||
return None;
|
||||
}
|
||||
config.snapshot
|
||||
}
|
||||
|
||||
pub fn describe_decision(config: &TimeFreezeConfig) -> TimeFreezeDebug {
|
||||
let decision = match &config.snapshot {
|
||||
None => TimeFreezeDecision::NoSnapshot,
|
||||
Some(_) if should_serve_frozen(config).is_some() => TimeFreezeDecision::Frozen,
|
||||
Some(_) => TimeFreezeDecision::Live,
|
||||
};
|
||||
TimeFreezeDebug {
|
||||
decision,
|
||||
snapshot_sha: config.snapshot.as_ref().map(|s| s.sha.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "time-freeze")]
|
||||
pub fn time_freeze_debug_header(config: &TimeFreezeConfig) -> Option<String> {
|
||||
if !config.enabled {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(format_debug_header(&describe_decision(config)))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "time-freeze"))]
|
||||
pub fn time_freeze_debug_header(_config: &TimeFreezeConfig) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn is_time_freeze_exempt_client(
|
||||
headers: &HeaderMap,
|
||||
trust_client_ip_header: bool,
|
||||
header_name: &str,
|
||||
) -> bool {
|
||||
extract_client_ip(headers, trust_client_ip_header, header_name)
|
||||
.is_some_and(|ip| ip == TIME_FREEZE_EXEMPT_CLIENT_IP)
|
||||
|| is_time_freeze_exempt_user_agent(headers)
|
||||
}
|
||||
|
||||
fn is_time_freeze_exempt_user_agent(headers: &HeaderMap) -> bool {
|
||||
let Some(user_agent) = headers
|
||||
.get(header::USER_AGENT)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
user_agent
|
||||
.match_indices(CANARY_USER_AGENT_PRODUCT)
|
||||
.any(|(index, _)| {
|
||||
let version_start = index + CANARY_USER_AGENT_PRODUCT.len();
|
||||
let version = user_agent[version_start..]
|
||||
.chars()
|
||||
.take_while(|ch| ch.is_ascii_digit() || *ch == '.')
|
||||
.collect::<String>();
|
||||
parse_calver(&version)
|
||||
.is_some_and(|version| version >= CANARY_LIVE_WEB_CLIENT_MIN_VERSION)
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_calver(value: &str) -> Option<CalVer> {
|
||||
let mut parts = value.split('.');
|
||||
let year = parts.next()?;
|
||||
let month_day = parts.next()?;
|
||||
let micro = parts.next()?;
|
||||
if parts.next().is_some()
|
||||
|| year.len() != 4
|
||||
|| !valid_nonzero_digits(year, 4)
|
||||
|| !(month_day.len() == 3 || month_day.len() == 4)
|
||||
|| !valid_nonzero_digits(month_day, month_day.len())
|
||||
|| !valid_micro(micro)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let year = year.parse().ok()?;
|
||||
let month_day_value: u16 = month_day.parse().ok()?;
|
||||
let month = month_day_value / 100;
|
||||
let day = month_day_value % 100;
|
||||
if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let micro = micro.parse().ok()?;
|
||||
let hour = micro / 10_000;
|
||||
let minute = (micro / 100) % 100;
|
||||
let second = micro % 100;
|
||||
if hour > 23 || minute > 59 || second > 59 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(CalVer {
|
||||
year,
|
||||
month_day: month_day_value,
|
||||
micro,
|
||||
})
|
||||
}
|
||||
|
||||
fn valid_nonzero_digits(value: &str, max_len: usize) -> bool {
|
||||
!value.is_empty()
|
||||
&& value.len() <= max_len
|
||||
&& !value.starts_with('0')
|
||||
&& value.bytes().all(|byte| byte.is_ascii_digit())
|
||||
}
|
||||
|
||||
fn valid_micro(value: &str) -> bool {
|
||||
value == "0" || valid_nonzero_digits(value, 6)
|
||||
}
|
||||
|
||||
fn extract_client_ip(
|
||||
headers: &HeaderMap,
|
||||
trust_client_ip_header: bool,
|
||||
header_name: &str,
|
||||
) -> Option<IpAddr> {
|
||||
if !trust_client_ip_header {
|
||||
return None;
|
||||
}
|
||||
let header_value = headers.get(header_name)?.to_str().ok()?;
|
||||
let first_hop = header_value.split(',').next()?.trim();
|
||||
parse_ip(first_hop)
|
||||
}
|
||||
|
||||
fn parse_ip(value: &str) -> Option<IpAddr> {
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if let Ok(ip) = value.parse() {
|
||||
return Some(ip);
|
||||
}
|
||||
if let Some(stripped) = value
|
||||
.strip_prefix('[')
|
||||
.and_then(|value| value.split_once(']').map(|(host, _)| host))
|
||||
{
|
||||
return stripped.parse().ok();
|
||||
}
|
||||
if value.matches(':').count() == 1
|
||||
&& let Some((host, _port)) = value.rsplit_once(':')
|
||||
{
|
||||
return host.parse().ok();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn format_debug_header(debug: &TimeFreezeDebug) -> String {
|
||||
let decision_str = match debug.decision {
|
||||
TimeFreezeDecision::Frozen => "frozen",
|
||||
TimeFreezeDecision::Live => "live",
|
||||
TimeFreezeDecision::NoSnapshot => "no-snapshot",
|
||||
};
|
||||
match &debug.snapshot_sha {
|
||||
Some(sha) => format!("{decision_str}; sha={sha}"),
|
||||
None => decision_str.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use axum::http::{HeaderMap, HeaderValue};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[cfg(feature = "time-freeze")]
|
||||
#[test]
|
||||
fn stable_channel_has_frozen_snapshot() {
|
||||
let config = load_time_freeze_config(ReleaseChannel::Stable, false);
|
||||
assert!(
|
||||
config.snapshot.is_some(),
|
||||
"stable channel should have a frozen snapshot"
|
||||
);
|
||||
assert!(should_serve_frozen(&config).is_some());
|
||||
}
|
||||
|
||||
#[cfg(feature = "time-freeze")]
|
||||
#[test]
|
||||
fn canary_channel_has_no_frozen_snapshot() {
|
||||
let config = load_time_freeze_config(ReleaseChannel::Canary, false);
|
||||
assert_eq!(config.release_channel, ReleaseChannel::Canary);
|
||||
assert!(config.snapshot.is_none());
|
||||
assert!(should_serve_frozen(&config).is_none());
|
||||
|
||||
let debug = describe_decision(&config);
|
||||
assert_eq!(debug.decision, TimeFreezeDecision::NoSnapshot);
|
||||
assert!(debug.snapshot_sha.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn index_upstream_disables_frozen_snapshot() {
|
||||
let config =
|
||||
load_time_freeze_config_for_index_source(ReleaseChannel::Canary, false, true, true);
|
||||
assert!(config.enabled);
|
||||
assert_eq!(config.release_channel, ReleaseChannel::Canary);
|
||||
assert!(config.snapshot.is_none());
|
||||
assert!(should_serve_frozen(&config).is_none());
|
||||
|
||||
let debug = describe_decision(&config);
|
||||
assert_eq!(debug.decision, TimeFreezeDecision::NoSnapshot);
|
||||
assert!(debug.snapshot_sha.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_time_freeze_has_no_snapshot_or_debug_header() {
|
||||
let config =
|
||||
load_time_freeze_config_for_index_source(ReleaseChannel::Stable, false, false, false);
|
||||
|
||||
assert!(!config.enabled);
|
||||
assert!(config.snapshot.is_none());
|
||||
assert!(should_serve_frozen(&config).is_none());
|
||||
assert!(time_freeze_debug_header(&config).is_none());
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "time-freeze"))]
|
||||
#[test]
|
||||
fn no_feature_build_has_no_frozen_snapshot() {
|
||||
let config = load_time_freeze_config(ReleaseChannel::Stable, false);
|
||||
|
||||
assert!(config.enabled);
|
||||
assert!(config.snapshot.is_none());
|
||||
assert!(should_serve_frozen(&config).is_none());
|
||||
assert!(time_freeze_debug_header(&config).is_none());
|
||||
}
|
||||
|
||||
#[cfg(feature = "time-freeze")]
|
||||
#[test]
|
||||
fn exempt_client_gets_live_content_even_with_snapshot() {
|
||||
let config = load_time_freeze_config(ReleaseChannel::Stable, true);
|
||||
assert!(config.snapshot.is_some());
|
||||
assert!(should_serve_frozen(&config).is_none());
|
||||
|
||||
let debug = describe_decision(&config);
|
||||
assert_eq!(debug.decision, TimeFreezeDecision::Live);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn describe_decision_no_snapshot() {
|
||||
let config = TimeFreezeConfig {
|
||||
enabled: true,
|
||||
release_channel: ReleaseChannel::Stable,
|
||||
snapshot: None,
|
||||
client_exempt: false,
|
||||
};
|
||||
let debug = describe_decision(&config);
|
||||
assert_eq!(debug.decision, TimeFreezeDecision::NoSnapshot);
|
||||
assert!(debug.snapshot_sha.is_none());
|
||||
}
|
||||
|
||||
static TEST_SNAPSHOT: LazyLock<FrozenSnapshot> = LazyLock::new(|| FrozenSnapshot {
|
||||
sha: "deadbeef".to_owned(),
|
||||
index_html: vec![],
|
||||
sw_js: vec![],
|
||||
version_json: vec![],
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn describe_decision_frozen() {
|
||||
let config = TimeFreezeConfig {
|
||||
enabled: true,
|
||||
release_channel: ReleaseChannel::Stable,
|
||||
snapshot: Some(&TEST_SNAPSHOT),
|
||||
client_exempt: false,
|
||||
};
|
||||
let debug = describe_decision(&config);
|
||||
assert_eq!(debug.decision, TimeFreezeDecision::Frozen);
|
||||
assert_eq!(debug.snapshot_sha.as_deref(), Some("deadbeef"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_debug_header_no_snapshot() {
|
||||
let debug = TimeFreezeDebug {
|
||||
decision: TimeFreezeDecision::NoSnapshot,
|
||||
snapshot_sha: None,
|
||||
};
|
||||
assert_eq!(format_debug_header(&debug), "no-snapshot");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_debug_header_frozen() {
|
||||
let debug = TimeFreezeDebug {
|
||||
decision: TimeFreezeDecision::Frozen,
|
||||
snapshot_sha: Some("abc".to_owned()),
|
||||
};
|
||||
assert_eq!(format_debug_header(&debug), "frozen; sha=abc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_debug_header_live() {
|
||||
let debug = TimeFreezeDebug {
|
||||
decision: TimeFreezeDecision::Live,
|
||||
snapshot_sha: Some("def".to_owned()),
|
||||
};
|
||||
assert_eq!(format_debug_header(&debug), "live; sha=def");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recognizes_exempt_x_real_ip_when_trusted() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("x-real-ip", HeaderValue::from_static("188.149.230.148"));
|
||||
|
||||
assert!(is_time_freeze_exempt_client(&headers, true, "x-real-ip"));
|
||||
assert!(!is_time_freeze_exempt_client(&headers, false, "x-real-ip"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recognizes_exempt_first_forwarded_ip() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
"x-forwarded-for",
|
||||
HeaderValue::from_static("188.149.230.148, 10.0.0.4"),
|
||||
);
|
||||
|
||||
assert!(is_time_freeze_exempt_client(
|
||||
&headers,
|
||||
true,
|
||||
"x-forwarded-for"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_exempt_ip_stays_frozen() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("x-real-ip", HeaderValue::from_static("203.0.113.10"));
|
||||
|
||||
assert!(!is_time_freeze_exempt_client(&headers, true, "x-real-ip"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canary_user_agent_at_min_version_is_exempt() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::USER_AGENT,
|
||||
HeaderValue::from_static("FluxerCanary/2026.614.0"),
|
||||
);
|
||||
|
||||
assert!(is_time_freeze_exempt_client(&headers, false, "x-real-ip"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canary_user_agent_newer_than_min_version_is_exempt() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::USER_AGENT,
|
||||
HeaderValue::from_static("Mozilla/5.0 FluxerCanary/2026.614.83512 Electron/99.0"),
|
||||
);
|
||||
|
||||
assert!(is_time_freeze_exempt_client(&headers, false, "x-real-ip"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canary_user_agent_before_min_version_stays_frozen() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::USER_AGENT,
|
||||
HeaderValue::from_static("FluxerCanary/2026.613.235959"),
|
||||
);
|
||||
|
||||
assert!(!is_time_freeze_exempt_client(&headers, false, "x-real-ip"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_canary_user_agent_stays_frozen() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::USER_AGENT,
|
||||
HeaderValue::from_static("FluxerCanary/2026.614.not-a-calver"),
|
||||
);
|
||||
|
||||
assert!(!is_time_freeze_exempt_client(&headers, false, "x-real-ip"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stable_user_agent_stays_frozen() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::USER_AGENT,
|
||||
HeaderValue::from_static("FluxerStable/2026.614.83512"),
|
||||
);
|
||||
|
||||
assert!(!is_time_freeze_exempt_client(&headers, false, "x-real-ip"));
|
||||
}
|
||||
}
|
||||
@@ -1559,10 +1559,6 @@ Default `/api`. The API path put in the page bootstrap.
|
||||
|
||||
No default. The absolute API URL in the bootstrap. Compose builds it from the public origin.
|
||||
|
||||
#### `FLUXER_APP_PROXY_TIME_FREEZE_ENABLED`
|
||||
|
||||
Defaults to the inverse of `FLUXER_SELF_HOSTED`. A frozen asset snapshot. Also compiled out of the self-hosted image.
|
||||
|
||||
## Admin settings
|
||||
|
||||
`admin` serves the dashboard at `/admin`. All are optional.
|
||||
|
||||
@@ -19,7 +19,6 @@ use std::fs::{self, File};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const DEFAULT_PUBLIC_ASSET_BASE_URL: &str = "https://fluxerstatic.com";
|
||||
const DEFAULT_APP_PROXY_TIME_FREEZE_ENABLED: &str = "true";
|
||||
const DEFAULT_APP_PROXY_BUNDLE_LOCAL_ASSETS: &str = "false";
|
||||
const DEFAULT_STATIC_BUCKET: &str = "fluxer-static";
|
||||
const DEFAULT_S3_ENDPOINT: &str = "https://ewr1.vultrobjects.com";
|
||||
@@ -178,11 +177,6 @@ fn bake_command(targets: &[&str]) -> Result<CommandSpec> {
|
||||
.env("IMAGE_REPO", image_repo()?)
|
||||
.env("BUILD_VERSION", build_version)
|
||||
.env("PUBLIC_ASSET_BASE_URL", public_asset_base_url)
|
||||
.env(
|
||||
"FLUXER_APP_PROXY_TIME_FREEZE_ENABLED",
|
||||
env::var("FLUXER_APP_PROXY_TIME_FREEZE_ENABLED")
|
||||
.unwrap_or_else(|_| DEFAULT_APP_PROXY_TIME_FREEZE_ENABLED.to_string()),
|
||||
)
|
||||
.env(
|
||||
"BUNDLE_LOCAL_ASSETS",
|
||||
env::var("BUNDLE_LOCAL_ASSETS")
|
||||
@@ -636,11 +630,7 @@ mod tests {
|
||||
.args(["buildx", "bake", "-f", "fluxer_app_proxy/docker-bake.hcl"])
|
||||
.env("IMAGE_REPO", "ghcr.io/example/fluxer-app-proxy")
|
||||
.env("BUILD_VERSION", "2026.520.1")
|
||||
.env("PUBLIC_ASSET_BASE_URL", DEFAULT_PUBLIC_ASSET_BASE_URL)
|
||||
.env(
|
||||
"FLUXER_APP_PROXY_TIME_FREEZE_ENABLED",
|
||||
DEFAULT_APP_PROXY_TIME_FREEZE_ENABLED,
|
||||
);
|
||||
.env("PUBLIC_ASSET_BASE_URL", DEFAULT_PUBLIC_ASSET_BASE_URL);
|
||||
|
||||
assert_eq!(command.program, OsString::from("docker"));
|
||||
assert_eq!(
|
||||
@@ -656,10 +646,6 @@ mod tests {
|
||||
OsString::from("BUILD_VERSION"),
|
||||
OsString::from("2026.520.1")
|
||||
)));
|
||||
assert!(command.env.contains(&(
|
||||
OsString::from("FLUXER_APP_PROXY_TIME_FREEZE_ENABLED"),
|
||||
OsString::from(DEFAULT_APP_PROXY_TIME_FREEZE_ENABLED)
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "fluxer-content-update-frozen-snapshot"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.104"
|
||||
base64 = "0.23.1"
|
||||
sha2 = "0.11.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.27.0"
|
||||
@@ -1,158 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use base64::Engine;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn generate_snapshot_source(static_dir: &Path) -> Result<String> {
|
||||
if !static_dir.is_dir() {
|
||||
bail!("{} is not a directory", static_dir.display());
|
||||
}
|
||||
|
||||
let index_html = read_file(&static_dir.join("index.html"))?;
|
||||
let sw_js = read_file(&static_dir.join("sw.js"))?;
|
||||
let version_json = read_file(&static_dir.join("version.json"))?;
|
||||
let sha = snapshot_sha(&index_html, &sw_js, &version_json);
|
||||
|
||||
let mut output = String::new();
|
||||
output.push_str(&format!("const SNAPSHOT_SHA: &str = \"{sha}\";\n\n"));
|
||||
output.push_str(&format!(
|
||||
"// -- base64-encoded index.html ({} bytes) --\n",
|
||||
index_html.len()
|
||||
));
|
||||
output.push_str(&format_const(
|
||||
"STABLE_INDEX_HTML",
|
||||
&base64_standard(&index_html),
|
||||
));
|
||||
output.push_str(&format!(
|
||||
"// -- base64-encoded sw.js ({} bytes) --\n",
|
||||
sw_js.len()
|
||||
));
|
||||
output.push_str(&format_const("STABLE_SW_JS", &base64_standard(&sw_js)));
|
||||
output.push_str(&format!(
|
||||
"// -- base64-encoded version.json ({} bytes) --\n",
|
||||
version_json.len()
|
||||
));
|
||||
output.push_str(&format_const(
|
||||
"STABLE_VERSION_JSON",
|
||||
&base64_standard(&version_json),
|
||||
));
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn read_file(path: &Path) -> Result<Vec<u8>> {
|
||||
if !path.exists() {
|
||||
bail!("{} does not exist", path.display());
|
||||
}
|
||||
fs::read(path).with_context(|| format!("failed to read {}", path.display()))
|
||||
}
|
||||
|
||||
fn snapshot_sha(index_html: &[u8], sw_js: &[u8], version_json: &[u8]) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(index_html);
|
||||
hasher.update(sw_js);
|
||||
hasher.update(version_json);
|
||||
bytes_to_lower_hex(&hasher.finalize())
|
||||
}
|
||||
|
||||
fn bytes_to_lower_hex(bytes: &[u8]) -> String {
|
||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||
let mut out = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
out.push(HEX[(byte >> 4) as usize] as char);
|
||||
out.push(HEX[(byte & 0x0f) as usize] as char);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn base64_standard(data: &[u8]) -> String {
|
||||
base64::engine::general_purpose::STANDARD.encode(data)
|
||||
}
|
||||
|
||||
fn break_lines(value: &str, width: usize) -> Vec<&str> {
|
||||
value
|
||||
.as_bytes()
|
||||
.chunks(width)
|
||||
.map(|chunk| std::str::from_utf8(chunk).expect("base64 should be ascii"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn format_const(name: &str, data: &str) -> String {
|
||||
let escaped = break_lines(data, 100).join("\\\n");
|
||||
format!("const {name}: &str = \"\\\n{escaped}\\\n\";\n")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn emits_snapshot_constants_with_combined_sha() {
|
||||
let dir = tempdir().unwrap();
|
||||
let index_html = b"<!doctype html><html><body>fixture</body></html>";
|
||||
let sw_js = b"\"use strict\";\nself.addEventListener('install', () => {});\n";
|
||||
let version_json = br#"{"sha":"fixture","buildNumber":7}"#;
|
||||
fs::write(dir.path().join("index.html"), index_html).unwrap();
|
||||
fs::write(dir.path().join("sw.js"), sw_js).unwrap();
|
||||
fs::write(dir.path().join("version.json"), version_json).unwrap();
|
||||
|
||||
let output = generate_snapshot_source(dir.path()).unwrap();
|
||||
let expected_sha = snapshot_sha(index_html, sw_js, version_json);
|
||||
|
||||
assert!(output.starts_with(&format!(
|
||||
"const SNAPSHOT_SHA: &str = \"{expected_sha}\";\n\n"
|
||||
)));
|
||||
assert!(output.contains("// -- base64-encoded index.html (48 bytes) --"));
|
||||
assert!(output.contains("// -- base64-encoded sw.js (58 bytes) --"));
|
||||
assert!(output.contains("// -- base64-encoded version.json (33 bytes) --"));
|
||||
assert!(output.contains(&format_const(
|
||||
"STABLE_INDEX_HTML",
|
||||
&base64_standard(index_html)
|
||||
)));
|
||||
assert!(output.contains(&format_const("STABLE_SW_JS", &base64_standard(sw_js))));
|
||||
assert!(output.contains(&format_const(
|
||||
"STABLE_VERSION_JSON",
|
||||
&base64_standard(version_json)
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wraps_base64_constants_at_100_columns() {
|
||||
let data = "a".repeat(205);
|
||||
let formatted = format_const("TEST", &data);
|
||||
let lines = formatted.lines().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(lines[0], "const TEST: &str = \"\\");
|
||||
assert_eq!(lines[1], format!("{}\\", "a".repeat(100)));
|
||||
assert_eq!(lines[2], format!("{}\\", "a".repeat(100)));
|
||||
assert_eq!(lines[3], format!("{}\\", "a".repeat(5)));
|
||||
assert_eq!(lines[4], "\";");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_non_directory_static_path() {
|
||||
let dir = tempdir().unwrap();
|
||||
let err = generate_snapshot_source(&dir.path().join("missing"))
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
assert!(err.contains("missing is not a directory"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_missing_required_file() {
|
||||
let dir = tempdir().unwrap();
|
||||
fs::write(dir.path().join("index.html"), b"index").unwrap();
|
||||
|
||||
let err = generate_snapshot_source(dir.path())
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
assert!(err.contains("sw.js does not exist"));
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use anyhow::Result;
|
||||
use fluxer_content_update_frozen_snapshot::generate_snapshot_source;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("error: {err:#}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<()> {
|
||||
let mut args = env::args_os();
|
||||
let program = args
|
||||
.next()
|
||||
.and_then(|value| PathBuf::from(value).file_name().map(|name| name.to_owned()))
|
||||
.and_then(|name| name.into_string().ok())
|
||||
.unwrap_or_else(|| "fluxer-content-update-frozen-snapshot".to_owned());
|
||||
|
||||
let Some(static_dir) = args.next() else {
|
||||
eprintln!("usage: {program} <static_dir>");
|
||||
process::exit(1);
|
||||
};
|
||||
if args.next().is_some() {
|
||||
eprintln!("usage: {program} <static_dir>");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let output = generate_snapshot_source(&PathBuf::from(static_dir))?;
|
||||
print!("{output}");
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user