CH-J Toolsupravljanje poslužiteljima preko SSH
Meni
Objavljen izvor

CH-J Tools

Pregledaj direktorije i datoteke za određeni programski izdanje.

Preuzmi ZIP izvora
CH-J / Server Manager 1704 datoteke
0.1.2-beta Dodano: · Izgrađeno:
url_policy.rs Dodano: Datum nije zabilježen 4,4 KB · 89 linije
Oznaka Preuzmi datoteku
1//! HTTPS allowlist for update and module catalogs (port of `security/urlPolicy.js`).
3use crate::core::error::{CoreError, CoreResult};
4use url::Url;
6pub fn normalize_base_url(value: &str) -> CoreResult<Url> {
7 let mut url = Url::parse(value).map_err(|_| CoreError::plain("Invalid update base URL."))?;
8 if url.scheme() != "https" {
9 return Err(CoreError::plain("Update URL must use HTTPS."));
10 }
11 if !url.username().is_empty() || url.password().is_some() || url.fragment().is_some() || url.query().is_some() {
12 return Err(CoreError::plain("Invalid update base URL."));
13 }
14 let path = format!("{}/", url.path().trim_end_matches('/'));
15 url.set_path(&path);
16 Ok(url)
19#[derive(Default)]
20pub struct AllowOptions<'a> {
21 pub base_url: Option<&'a str>,
22 pub path_segment: Option<&'a str>,
25pub fn assert_allowed_url(candidate: &str, allowed: &[String], options: &AllowOptions<'_>) -> CoreResult<Url> {
26 let raw = candidate.trim();
27 if raw.is_empty() {
28 return Err(CoreError::plain("Missing URL."));
29 }
30 let bases = allowed.iter().map(|b| normalize_base_url(b)).collect::<CoreResult<Vec<_>>>()?;
31 if raw.starts_with('/') {
32 let base = normalize_base_url(options.base_url.ok_or_else(|| CoreError::plain("Relative URL requires a base URL."))?)?;
33 let joined = base.join(raw.trim_start_matches('/')).map_err(|_| CoreError::plain("Invalid URL."))?;
34 return assert_allowed_url(joined.as_str(), allowed, options);
35 }
36 let url = Url::parse(raw).map_err(|_| CoreError::plain("Invalid URL."))?;
37 if !url.username().is_empty() || url.password().is_some() || url.fragment().is_some() {
38 return Err(CoreError::plain("URL credentials and fragments are not allowed."));
39 }
40 let matched = bases
41 .iter()
42 .find(|base| url.scheme() == base.scheme() && url.host_str() == base.host_str() && url.port() == base.port() && url.path().starts_with(base.path()))
43 .ok_or_else(|| CoreError::plain("URL is outside the update allowlist."))?;
44 if let Some(segment) = options.path_segment {
45 if !url.path().starts_with(&format!("{}{}", matched.path(), segment)) {
46 return Err(CoreError::plain("URL path is outside the allowed update area."));
47 }
48 }
49 Ok(url)
52/// External links the main window may open in the system browser (same rule as 0.1.0).
53pub fn is_allowed_external_url(value: &str) -> bool {
54 Url::parse(value).map(|u| u.scheme() == "https" && u.host_str() == Some("ch-j.de")).unwrap_or(false)
57#[cfg(test)]
58mod tests {
59 use super::*;
61 #[test]
62 fn allowlist() {
63 let allowed = vec!["https://sm.ch-j.de/".to_string()];
64 assert!(assert_allowed_url("https://sm.ch-j.de/files/a.dmg", &allowed, &AllowOptions { path_segment: Some("files/"), ..Default::default() }).is_ok());
65 assert!(assert_allowed_url("https://sm.ch-j.de/api/x", &allowed, &AllowOptions { path_segment: Some("files/"), ..Default::default() }).is_err());
66 assert!(assert_allowed_url("https://evil.example/files/a", &allowed, &AllowOptions::default()).is_err());
67 assert!(assert_allowed_url("https://u:p@sm.ch-j.de/x", &allowed, &AllowOptions::default()).is_err());
68 assert!(assert_allowed_url("http://sm.ch-j.de/x", &allowed, &AllowOptions::default()).is_err());
69 assert!(assert_allowed_url("https://sm.ch-j.de:444/x", &allowed, &AllowOptions::default()).is_err());
70 assert_eq!(
71 assert_allowed_url("/files/b.deb", &allowed, &AllowOptions { base_url: Some("https://sm.ch-j.de"), ..Default::default() }).unwrap().as_str(),
72 "https://sm.ch-j.de/files/b.deb"
73 );
74 assert!(is_allowed_external_url("https://ch-j.de/x"));
75 assert!(!is_allowed_external_url("http://ch-j.de/x"));
76 }
78 #[test]
79 fn both_update_hosts_and_nothing_alike() {
80 let allowed: Vec<String> = crate::core::config::UPDATE_BASE_URLS.iter().map(|u| u.to_string()).collect();
81 let files = AllowOptions { path_segment: Some("files/"), ..Default::default() };
82 for ok in ["https://sm.ch-j.de/files/a.deb", "https://tools.ch-j.de/files/a.deb"] {
83 assert!(assert_allowed_url(ok, &allowed, &files).is_ok(), "{ok}");
84 }
85 for bad in ["https://tools.ch-j.de.evil.example/files/a.deb", "https://eviltools.ch-j.de/files/a.deb", "http://tools.ch-j.de/files/a.deb", "https://ch-j.de/files/a.deb"] {
86 assert!(assert_allowed_url(bad, &allowed, &files).is_err(), "{bad}");
87 }
88 }
SHA-256a88ceab518eaf121f050b5055211a00ad7e7b96285b398c95f43a9e3ebdeb34e

CH-J Proprietary Software License 1.14 · Izvor je osiguran pod CH-J Proprietary Software License 1.14. Njena dostupnost ne mijenja uvjete licenciranja niti daje dodatna prava.

Arhiv SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
Datoteke