Uverejnený zdroj
Stiahnuť zdroj ZIP
CH-J Tools
Prechádzať adresáre a súbory pre konkrétne vydanie aplikácie.
CH-J / Server Manager
1704 súbory
0.1.2-beta
Pridané: · Postavený:
1
//! `chj-module://<module-id>/<path>` (Windows: `http://chj-module.<module-id>.localhost/<path>`):2
//! serves only files of the module that owns the requesting window, plus the shared3
//! window-chrome stylesheet. Port of `PluginRuntime.handleRequest` with stricter checks4
//! (canonical path containment, requesting window must belong to the module).6
use crate::core::app::CoreSlot;7
use crate::core::modules::service::MODULE_CSP;8
use std::path::{Component, Path, PathBuf};9
use tauri::http::{Request, Response, StatusCode};10
use tauri::{Manager, Runtime, UriSchemeContext};12
const MAX_RESOURCE: u64 = 10 * 1024 * 1024;14
fn mime(path: &Path) -> &'static str {15
match path.extension().map(|e| e.to_string_lossy().to_lowercase()).as_deref() {16
Some("css") => "text/css; charset=utf-8",17
Some("gif") => "image/gif",18
Some("html") => "text/html; charset=utf-8",19
Some("jpeg") | Some("jpg") => "image/jpeg",20
Some("js") => "text/javascript; charset=utf-8",21
Some("json") => "application/json; charset=utf-8",22
Some("png") => "image/png",23
Some("svg") => "image/svg+xml",24
Some("webp") => "image/webp",25
Some("woff") => "font/woff",26
Some("woff2") => "font/woff2",27
Some("ttf") => "font/ttf",28
_ => "application/octet-stream",29
}30
}32
fn not_found() -> Response<Vec<u8>> {33
Response::builder().status(StatusCode::NOT_FOUND).header("Content-Type", "text/plain").body(b"Not found".to_vec()).unwrap()34
}36
/// Splits the request into (module id, relative path) for both URL forms.37
pub fn split_request(uri: &str) -> Option<(String, String)> {38
let url = url::Url::parse(uri).ok()?;39
let host = url.host_str()?.to_string();40
let segments: Vec<String> = url.path_segments()?.map(|s| percent_decode(s)).collect::<Option<Vec<_>>>()?;41
let id = host.strip_prefix("chj-module.").unwrap_or(&host);42
let id = id.strip_suffix(".localhost").unwrap_or(id);43
Some((id.to_string(), segments.join("/")))44
}46
fn percent_decode(segment: &str) -> Option<String> {47
let bytes = segment.as_bytes();48
let mut out = Vec::with_capacity(bytes.len());49
let mut i = 0;50
while i < bytes.len() {51
if bytes[i] == b'%' {52
let hex = std::str::from_utf8(bytes.get(i + 1..i + 3)?).ok()?;53
out.push(u8::from_str_radix(hex, 16).ok()?);54
i += 3;55
} else {56
out.push(bytes[i]);57
i += 1;58
}59
}60
String::from_utf8(out).ok()61
}63
fn safe_relative(requested: &str) -> Option<PathBuf> {64
if requested.is_empty() || requested.starts_with('.') || requested.contains('\\') {65
return None;66
}67
let path = PathBuf::from(requested);68
for component in path.components() {69
match component {70
Component::Normal(part) if !part.to_string_lossy().starts_with('.') => {}71
_ => return None,72
}73
}74
Some(path)75
}77
pub fn handle<R: Runtime>(context: UriSchemeContext<'_, R>, request: Request<Vec<u8>>) -> Response<Vec<u8>> {78
let app = context.app_handle();79
let Some(core) = app.state::<CoreSlot>().try_get() else { return not_found() };80
let Some((module_id, requested)) = split_request(&request.uri().to_string()) else { return not_found() };81
// The requesting window must be the module's own window.82
match core.windows.module_by_label(context.webview_label()) {83
Some(owner) if owner.id == module_id => {}84
_ => {85
core.logger.warn("Plugin resource request rejected.", serde_json::json!({ "url": request.uri().to_string(), "message": "foreign window" }));86
return not_found();87
}88
}89
let shared = requested.strip_prefix("__chj_core__/").filter(|name| {90
["window-chrome.css", "theme.css"].contains(name) || name.strip_prefix("fonts/").is_some_and(|font| font.ends_with(".woff2") && !font.contains(['/', '\\']))91
});92
let (root, relative) = if let Some(name) = shared {93
(core.paths.bundled_modules(), PathBuf::from(name))94
} else {95
let Ok(resolved) = core.services.modules.registry.resolve(&module_id) else { return not_found() };96
let entry = resolved.manifest["entry"].as_str().unwrap_or("").to_string();97
let Some(relative) = safe_relative(if requested.is_empty() { &entry } else { &requested }) else { return not_found() };98
(resolved.root, relative)99
};100
let file = root.join(&relative);101
let (Ok(real_root), Ok(real_file)) = (std::fs::canonicalize(&root), std::fs::canonicalize(&file)) else { return not_found() };102
if !real_file.starts_with(&real_root) {103
return not_found();104
}105
match std::fs::metadata(&real_file) {106
Ok(meta) if meta.is_file() && meta.len() <= MAX_RESOURCE => {}107
_ => return not_found(),108
}109
let Ok(data) = std::fs::read(&real_file) else { return not_found() };110
Response::builder()111
.status(StatusCode::OK)112
.header("Content-Type", mime(&real_file))113
.header("Content-Security-Policy", MODULE_CSP)114
.header("Cache-Control", "no-store")115
.header("X-Content-Type-Options", "nosniff")116
.header("Referrer-Policy", "no-referrer")117
.body(data)118
.unwrap_or_else(|_| not_found())119
}121
#[cfg(test)]122
mod tests {123
use super::*;125
#[test]126
fn request_splitting() {127
assert_eq!(split_request("chj-module://chj.users/ui/index.html"), Some(("chj.users".into(), "ui/index.html".into())));128
assert_eq!(split_request("http://chj-module.chj.users.localhost/ui/a%20b.js"), Some(("chj.users".into(), "ui/a b.js".into())));129
assert!(safe_relative("../x").is_none());130
assert!(safe_relative("ui/.hidden").is_none());131
assert!(safe_relative("ui/index.html").is_some());132
}133
}SHA-256
6a7f528782673d284e7417e27cd1cbf3c30295f6d293b7fba52503da79b8ca1eCH-J Proprietary Software License 1.14 · Zdroj sa poskytuje v rámci CH-J Proprietary Software License 1.14. Jeho dostupnosť nemení licenčné podmienky ani neposkytuje dodatočné práva.
Archív SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f