Skip to main content

rws_proxy/traffic/
config.rs

1//! `[traffic]` settings (`RWS_CONFIG_TRAFFIC_*`).
2
3use std::path::PathBuf;
4
5pub const RWS_CONFIG_TRAFFIC_ENABLED: &str = "RWS_CONFIG_TRAFFIC_ENABLED";
6pub const RWS_CONFIG_TRAFFIC_RETENTION_DAYS: &str = "RWS_CONFIG_TRAFFIC_RETENTION_DAYS";
7pub const RWS_CONFIG_TRAFFIC_PERSIST: &str = "RWS_CONFIG_TRAFFIC_PERSIST";
8pub const RWS_CONFIG_TRAFFIC_EXCLUDE: &str = "RWS_CONFIG_TRAFFIC_EXCLUDE";
9pub const RWS_CONFIG_TRAFFIC_GEO_DB: &str = "RWS_CONFIG_TRAFFIC_GEO_DB";
10pub const RWS_CONFIG_TRAFFIC_SCRIPT: &str = "RWS_CONFIG_TRAFFIC_SCRIPT";
11pub const RWS_CONFIG_TRAFFIC_GOALS: &str = "RWS_CONFIG_TRAFFIC_GOALS";
12pub const RWS_CONFIG_TRAFFIC_DATABASE_URL_ENV: &str = "RWS_CONFIG_TRAFFIC_DATABASE_URL_ENV";
13pub const RWS_CONFIG_TRAFFIC_HOSTING_NETWORKS: &str = "RWS_CONFIG_TRAFFIC_HOSTING_NETWORKS";
14pub const RWS_CONFIG_TRAFFIC_APPS: &str = "RWS_CONFIG_TRAFFIC_APPS";
15pub const RWS_CONFIG_TRAFFIC_OWNER_NETWORKS: &str = "RWS_CONFIG_TRAFFIC_OWNER_NETWORKS";
16pub const RWS_CONFIG_TRAFFIC_PROBE_ADDRESSES: &str = "RWS_CONFIG_TRAFFIC_PROBE_ADDRESSES";
17pub const RWS_CONFIG_TRAFFIC_HIDE_HOSTS: &str = "RWS_CONFIG_TRAFFIC_HIDE_HOSTS";
18pub const RWS_CONFIG_TRAFFIC_IGNORE_REFERRERS: &str = "RWS_CONFIG_TRAFFIC_IGNORE_REFERRERS";
19
20fn read(key: &str) -> String {
21    std::env::var(key).unwrap_or_default().trim().to_string()
22}
23
24/// `enabled`: `Some(false)` turns counting off even with the admin API on;
25/// `None` when unset.
26pub fn enabled_setting() -> Option<bool> {
27    match read(RWS_CONFIG_TRAFFIC_ENABLED).to_ascii_lowercase().as_str() {
28        "" => None,
29        "false" | "0" | "no" | "off" => Some(false),
30        _ => Some(true),
31    }
32}
33
34/// `retention_days`: 1–90, default 30.
35pub fn retention_days() -> u64 {
36    read(RWS_CONFIG_TRAFFIC_RETENTION_DAYS).parse::<u64>().ok().filter(|d| *d >= 1).map_or(30, |d| d.min(90))
37}
38
39/// `exclude`: exact paths not counted (comma-separated); default the
40/// health, readiness and metrics endpoints.
41pub fn exclude() -> Vec<String> {
42    let value = std::env::var(RWS_CONFIG_TRAFFIC_EXCLUDE);
43    match value {
44        Ok(v) => v.split(',').map(str::trim).filter(|p| !p.is_empty()).map(str::to_string).collect(),
45        Err(_) => ["/healthz", "/readyz", "/metrics"].iter().map(|p| p.to_string()).collect(),
46    }
47}
48
49/// `persist`: the file the counts are saved to. Unset: `traffic.json` next
50/// to the certificate (the ACME certificate's directory, else the TLS
51/// certificate's) — the state directory; without either, nothing is
52/// saved. `"off"`: not saved.
53pub fn persist_path() -> Option<PathBuf> {
54    let value = read(RWS_CONFIG_TRAFFIC_PERSIST);
55    if value.eq_ignore_ascii_case("off") {
56        return None;
57    }
58    if !value.is_empty() {
59        return Some(PathBuf::from(value));
60    }
61    [crate::entry_point::Config::RWS_CONFIG_ACME_CERT_PATH, crate::entry_point::Config::RWS_CONFIG_TLS_CERT_FILE]
62        .iter()
63        .map(|key| read(key))
64        .find(|path| !path.is_empty())
65        .and_then(|cert| PathBuf::from(cert).parent().map(|dir| dir.join("traffic.json")))
66}
67
68/// `geo_db`: a country table (`ci/gen_geo.py`'s format) to use instead of
69/// the built-in one; unset: the built-in one.
70pub fn geo_db() -> Option<PathBuf> {
71    let value = read(RWS_CONFIG_TRAFFIC_GEO_DB);
72    (!value.is_empty()).then(|| PathBuf::from(value))
73}
74
75/// `script`: serve the first-party script at `/_rws/t.js` and take its
76/// reports at `/_rws/t`. Default off.
77pub fn script() -> bool {
78    matches!(read(RWS_CONFIG_TRAFFIC_SCRIPT).to_ascii_lowercase().as_str(), "true" | "1" | "yes" | "on")
79}
80
81/// `hosting_networks`: count browsers from cloud and server hosting
82/// networks as bots (`traffic::hosting`). Default on; `false` counts them
83/// as people when they load assets, as before 18.35.
84pub fn hosting_networks() -> bool {
85    !matches!(read(RWS_CONFIG_TRAFFIC_HOSTING_NETWORKS).to_ascii_lowercase().as_str(), "false" | "0" | "no" | "off")
86}
87
88/// The most goals counted.
89pub const MAX_GOALS: usize = 32;
90
91/// `goals`: what counts as a conversion — a page path (`/thanks/`, or a
92/// prefix ending in `*`: `/docs/*`) or a script event (`event:signup`),
93/// comma-separated; at most [`MAX_GOALS`].
94pub fn goals() -> Vec<String> {
95    read(RWS_CONFIG_TRAFFIC_GOALS).split(',').map(str::trim).filter(|g| !g.is_empty()).take(MAX_GOALS).map(str::to_string).collect()
96}
97
98/// `database_url_env`: the environment variable holding a `postgres://` URL
99/// to write page views and script reports to (the URL itself stays out of
100/// the config file and the admin API). `None` when unset or the variable is
101/// empty.
102pub fn database_url() -> Option<String> {
103    let name = read(RWS_CONFIG_TRAFFIC_DATABASE_URL_ENV);
104    if name.is_empty() {
105        return None;
106    }
107    std::env::var(&name).ok().map(|v| v.trim().to_string()).filter(|v| !v.is_empty())
108}
109
110fn list(key: &str) -> Vec<String> {
111    read(key).split(',').map(str::trim).filter(|v| !v.is_empty()).map(str::to_string).collect()
112}
113
114/// `apps`: the site's native apps and browser extensions, as
115/// `name=pattern` (`traffic::apps`): a `User-Agent` token (`HITOP/`) or an
116/// extension origin (`chrome-extension://<id>`).
117pub fn apps() -> Vec<String> {
118    list(RWS_CONFIG_TRAFFIC_APPS)
119}
120
121/// `owner_networks`: addresses or CIDR ranges whose requests are the
122/// owner's (`traffic::owner`), counted apart from everyone else's.
123pub fn owner_networks() -> Vec<String> {
124    list(RWS_CONFIG_TRAFFIC_OWNER_NETWORKS)
125}
126
127/// `probe_addresses`: keep the addresses of scanners (requests for
128/// `/.env`, `/.git/config`…) in the probes list, to block them. Default
129/// off: no address is kept, only country and network.
130pub fn probe_addresses() -> bool {
131    matches!(read(RWS_CONFIG_TRAFFIC_PROBE_ADDRESSES).to_ascii_lowercase().as_str(), "true" | "1" | "yes" | "on")
132}
133
134/// `hide_hosts`: names routes serve that aren't sites to report (a mail
135/// server's name, an MTA-STS policy host): counted under `other`.
136pub fn hide_hosts() -> Vec<String> {
137    list(RWS_CONFIG_TRAFFIC_HIDE_HOSTS)
138}
139
140/// `ignore_referrers`: referring sites that are spam (a domain also covers
141/// its subdomains): a visit they send counts as direct, and they never
142/// show among the referrers.
143pub fn ignore_referrers() -> Vec<String> {
144    list(RWS_CONFIG_TRAFFIC_IGNORE_REFERRERS)
145}