CH-J ToolsSSH के ज़रिए सर्वर प्रबंधन
मेनू
प्रकाशित सोर्स

CH-J Tools

किसी विशेष ऐप्लिकेशन रिलीज़ की डायरेक्टरी और फ़ाइलें देखें।

सोर्स ZIP डाउनलोड करें
CH-J / Server Manager 1704 फ़ाइलें
0.1.2-beta जोड़ा गया: · बिल्ड किया गया:
protocol.rs जोड़ा गया: तारीख दर्ज नहीं 5.6 KB · 133 पंक्तियाँ
कोड फ़ाइल डाउनलोड करें
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 shared
3//! window-chrome stylesheet. Port of `PluginRuntime.handleRequest` with stricter checks
4//! (canonical path containment, requesting window must belong to the module).
6use crate::core::app::CoreSlot;
7use crate::core::modules::service::MODULE_CSP;
8use std::path::{Component, Path, PathBuf};
9use tauri::http::{Request, Response, StatusCode};
10use tauri::{Manager, Runtime, UriSchemeContext};
12const MAX_RESOURCE: u64 = 10 * 1024 * 1024;
14fn 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 }
32fn not_found() -> Response<Vec<u8>> {
33 Response::builder().status(StatusCode::NOT_FOUND).header("Content-Type", "text/plain").body(b"Not found".to_vec()).unwrap()
36/// Splits the request into (module id, relative path) for both URL forms.
37pub 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("/")))
46fn 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()
63fn 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)
77pub 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())
121#[cfg(test)]
122mod 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 }
SHA-2566a7f528782673d284e7417e27cd1cbf3c30295f6d293b7fba52503da79b8ca1e

CH-J Proprietary Software License 1.14 · सोर्स CH-J Proprietary Software License 1.14 के तहत उपलब्ध कराया जाता है। इसकी उपलब्धता लाइसेंस की शर्तें नहीं बदलती और न ही अतिरिक्त अधिकार देती है।

आर्काइव SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
फ़ाइलें