1use std::{
37 collections::HashMap,
38 panic::{AssertUnwindSafe, catch_unwind},
39 sync::RwLock,
40};
41
42use tauri::http::{
43 Method,
44 request::Request,
45 response::{Builder, Response},
46};
47
48use super::ServiceRegistry::ServiceRegistry;
49use crate::dev_log;
50
51static SERVICE_REGISTRY:RwLock<Option<ServiceRegistry>> = RwLock::new(None);
53
54pub fn init_service_registry(registry:ServiceRegistry) {
59 let mut registry_lock = SERVICE_REGISTRY.write().unwrap();
60
61 *registry_lock = Some(registry);
62}
63
64fn get_service_registry() -> Option<ServiceRegistry> {
76 let guard = SERVICE_REGISTRY.read().ok()?;
77
78 guard.clone()
79}
80
81#[derive(Clone, Debug)]
86pub struct DnsPort(pub u16);
87
88#[derive(Clone)]
90struct CacheEntry {
91 body:Vec<u8>,
93
94 content_type:String,
96
97 cache_control:String,
99
100 etag:Option<String>,
102
103 last_modified:Option<String>,
105}
106
107static CACHE:RwLock<Option<HashMap<String, CacheEntry>>> = RwLock::new(None);
115
116fn init_cache() {
118 let mut cache = CACHE.write().unwrap();
119
120 if cache.is_none() {
121 *cache = Some(HashMap::new());
122 }
123}
124
125fn get_cached(path:&str) -> Option<CacheEntry> {
127 let cache = CACHE.read().unwrap();
128
129 cache.as_ref()?.get(path).cloned()
130}
131
132fn set_cached(path:&str, entry:CacheEntry) {
134 let mut cache = CACHE.write().unwrap();
135
136 if let Some(cache) = cache.as_mut() {
137 cache.insert(path.to_string(), entry);
138 }
139}
140
141fn should_cache(path:&str) -> bool {
145 let path_lower = path.to_lowercase();
146
147 path_lower.ends_with(".css")
148 || path_lower.ends_with(".js")
149 || path_lower.ends_with(".png")
150 || path_lower.ends_with(".jpg")
151 || path_lower.ends_with(".jpeg")
152 || path_lower.ends_with(".gif")
153 || path_lower.ends_with(".svg")
154 || path_lower.ends_with(".woff")
155 || path_lower.ends_with(".woff2")
156 || path_lower.ends_with(".ttf")
157 || path_lower.ends_with(".eot")
158 || path_lower.ends_with(".ico")
159}
160
161fn parse_land_uri(uri:&str) -> Result<(String, String), String> {
182 let without_scheme = uri
184 .strip_prefix("land://")
185 .ok_or_else(|| format!("Invalid land:// URI: {}", uri))?;
186
187 let parts:Vec<&str> = without_scheme.splitn(2, '/').collect();
189
190 let domain = parts.get(0).ok_or_else(|| format!("No domain in URI: {}", uri))?.to_string();
191
192 let path = if parts.len() > 1 { format!("/{}", parts[1]) } else { "/".to_string() };
193
194 dev_log!("lifecycle", "[Scheme] Parsed URI: {} -> domain={}, path={}", uri, domain, path);
195
196 Ok((domain, path))
197}
198
199fn forward_http_request(
211 url:&str,
212
213 request:&Request<Vec<u8>>,
214
215 method:Method,
216) -> Result<(u16, Vec<u8>, HashMap<String, String>), String> {
217 let parsed_url = url.parse::<http::uri::Uri>().map_err(|e| format!("Invalid URL: {}", e))?;
219
220 let host = parsed_url.host().ok_or("No host in URL")?.to_string();
222
223 let port = parsed_url.port_u16().unwrap_or(80);
224
225 let path = parsed_url
226 .path_and_query()
227 .map(|p| p.as_str().to_string())
228 .unwrap_or_else(|| "/".to_string());
229
230 let addr = format!("{}:{}", host, port);
231
232 dev_log!("lifecycle", "[Scheme] Connecting to {} at {}", url, addr);
233
234 let body = request.body().clone();
236
237 let headers:Vec<(String, String)> = request
238 .headers()
239 .iter()
240 .filter_map(|(name, value)| {
241 let header_name = name.as_str().to_lowercase();
242 let hop_by_hop_headers = [
243 "connection",
244 "keep-alive",
245 "proxy-authenticate",
246 "proxy-authorization",
247 "te",
248 "trailers",
249 "transfer-encoding",
250 "upgrade",
251 ];
252 if !hop_by_hop_headers.contains(&header_name.as_str()) {
253 value.to_str().ok().map(|v| (name.as_str().to_string(), v.to_string()))
254 } else {
255 None
256 }
257 })
258 .collect();
259
260 let result = std::thread::spawn(move || {
262 let rt = tokio::runtime::Runtime::new().map_err(|e| format!("Failed to create runtime: {}", e))?;
263
264 rt.block_on(async {
265 use tokio::{
266 io::{AsyncReadExt, AsyncWriteExt},
267 net::TcpStream,
268 };
269
270 let mut stream = TcpStream::connect(&addr)
272 .await
273 .map_err(|e| format!("Failed to connect: {}", e))?;
274
275 let mut request_str = format!("{} {} HTTP/1.1\r\nHost: {}\r\n", method.as_str(), path, host);
277
278 for (name, value) in &headers {
280 request_str.push_str(&format!("{}: {}\r\n", name, value));
281 }
282
283 if !body.is_empty() {
285 request_str.push_str(&format!("Content-Length: {}\r\n", body.len()));
286 }
287
288 request_str.push_str("\r\n");
289
290 stream
292 .write_all(request_str.as_bytes())
293 .await
294 .map_err(|e| format!("Failed to write request: {}", e))?;
295
296 if !body.is_empty() {
297 stream
298 .write_all(&body)
299 .await
300 .map_err(|e| format!("Failed to write body: {}", e))?;
301 }
302
303 let mut buffer = Vec::new();
305 let mut temp_buf = [0u8; 8192];
306
307 loop {
308 let n = stream
309 .read(&mut temp_buf)
310 .await
311 .map_err(|e| format!("Failed to read response: {}", e))?;
312
313 if n == 0 {
314 break;
315 }
316
317 buffer.extend_from_slice(&temp_buf[..n]);
318
319 if buffer.len() > 1024 * 1024 {
322 dev_log!("lifecycle", "warn: [Scheme] Response too large, truncating");
324 break;
325 }
326
327 if let Some(headers_end) = buffer.windows(4).position(|w| w == b"\r\n\r\n") {
330 let headers = String::from_utf8_lossy(&buffer[..headers_end]);
331 if let Some(cl_line) = headers.lines().find(|l| l.to_lowercase().starts_with("content-length:")) {
332 if let Ok(cl) = cl_line.trim_start_matches("content-length:").trim().parse::<usize>() {
333 let body_expected = headers_end + 4 + cl;
334 if buffer.len() >= body_expected {
335 break;
336 }
337 }
338 } else if !headers.contains("Transfer-Encoding: chunked") {
339 continue;
341 }
342 }
343 }
344
345 let response_str = String::from_utf8_lossy(&buffer);
347 parse_http_response(&response_str)
348 })
349 })
350 .join()
351 .map_err(|e| format!("Thread panicked: {:?}", e))?;
352
353 result
354}
355
356fn parse_http_response(response:&str) -> Result<(u16, Vec<u8>, HashMap<String, String>), String> {
358 let headers_end = response
360 .find("\r\n\r\n")
361 .ok_or("Invalid HTTP response: no headers/body separator")?;
362
363 let headers_str = &response[..headers_end];
364
365 let body = response[headers_end + 4..].as_bytes().to_vec();
366
367 let mut lines = headers_str.lines();
369
370 let status_line = lines.next().ok_or("Invalid HTTP response: no status line")?;
371
372 let status = status_line
374 .split_whitespace()
375 .nth(1)
376 .and_then(|s| s.parse::<u16>().ok())
377 .ok_or_else(|| format!("Invalid status line: {}", status_line))?;
378
379 let mut headers = HashMap::new();
381
382 for line in lines {
383 if let Some((name, value)) = line.split_once(':') {
384 headers.insert(name.trim().to_lowercase(), value.trim().to_string());
385 }
386 }
387
388 Ok((status, body, headers))
389}
390
391pub fn land_scheme_handler(request:&Request<Vec<u8>>) -> Response<Vec<u8>> {
431 init_cache();
433
434 let uri = request.uri().to_string();
436
437 dev_log!("lifecycle", "[Scheme] Handling land:// request: {}", uri);
438
439 let (domain, path) = match parse_land_uri(&uri) {
441 Ok(result) => result,
442
443 Err(e) => {
444 dev_log!("lifecycle", "error: [Scheme] Failed to parse URI: {}", e);
445
446 return build_error_response(400, &format!("Bad Request: {}", e));
447 },
448 };
449
450 if request.method() == Method::OPTIONS {
452 dev_log!("lifecycle", "[Scheme] Handling CORS preflight request");
453
454 return build_cors_preflight_response();
455 }
456
457 if should_cache(&path) {
459 if let Some(cached) = get_cached(&path) {
460 dev_log!("lifecycle", "[Scheme] Cache hit for: {}", path);
461
462 return build_cached_response(cached);
463 }
464 }
465
466 let registry = match get_service_registry() {
468 Some(r) => r,
469
470 None => {
471 dev_log!("lifecycle", "error: [Scheme] Service registry not initialized");
472
473 return build_error_response(503, "Service Unavailable: Registry not initialized");
474 },
475 };
476
477 let service = match registry.lookup(&domain) {
478 Some(s) => s,
479
480 None => {
481 dev_log!("lifecycle", "warn: [Scheme] Service not found: {}", domain);
482
483 return build_error_response(404, &format!("Not Found: Service {} not registered", domain));
484 },
485 };
486
487 let local_url = format!("http://127.0.0.1:{}{}", service.port, path);
489
490 dev_log!(
491 "lifecycle",
492 "[Scheme] Routing {} {} to local service at {}",
493 request.method(),
494 uri,
495 local_url
496 );
497
498 let result = forward_http_request(&local_url, request, request.method().clone());
500
501 match result {
502 Ok((status, body, headers)) => {
503 let body_bytes = body.clone();
505
506 let LowerPath = path.to_ascii_lowercase();
517
518 let IsAssetRequest = LowerPath.ends_with(".js")
519 || LowerPath.ends_with(".mjs")
520 || LowerPath.ends_with(".cjs")
521 || LowerPath.ends_with(".json")
522 || LowerPath.ends_with(".map")
523 || LowerPath.ends_with(".css")
524 || LowerPath.ends_with(".wasm")
525 || LowerPath.ends_with(".svg")
526 || LowerPath.ends_with(".png")
527 || LowerPath.ends_with(".woff")
528 || LowerPath.ends_with(".woff2")
529 || LowerPath.ends_with(".ttf")
530 || LowerPath.ends_with(".otf");
531
532 let UpstreamSaysHtml = headers
533 .get("content-type")
534 .map(|V| V.to_ascii_lowercase().contains("text/html"))
535 .unwrap_or(false);
536
537 if IsAssetRequest && (status == 404 || (status >= 400 && UpstreamSaysHtml)) {
538 dev_log!(
539 "scheme-assets",
540 "[LandFix:Mime] swap HTML 404 → text/plain empty for asset path={} status={}",
541 path,
542 status
543 );
544
545 return Builder::new()
546 .status(404)
547 .header("Content-Type", "text/plain; charset=utf-8")
548 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
549 .body(Vec::<u8>::new())
550 .unwrap_or_else(|_| build_error_response(500, "Failed to build 404 response"));
551 }
552
553 let mut response_builder = Builder::new()
555 .status(status)
556 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
557 .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
558 .header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
559
560 let important_headers = [
562 "content-type",
563 "content-length",
564 "etag",
565 "last-modified",
566 "cache-control",
567 "expires",
568 "content-encoding",
569 "content-disposition",
570 "location",
571 ];
572
573 for header_name in &important_headers {
574 if let Some(value) = headers.get(*header_name) {
575 response_builder = response_builder.header(*header_name, value);
576 }
577 }
578
579 let response = response_builder.body(body_bytes);
580
581 if status == 200 && should_cache(&path) {
583 let content_type = headers
584 .get("content-type")
585 .unwrap_or(&"application/octet-stream".to_string())
586 .clone();
587
588 let cache_control = headers
589 .get("cache-control")
590 .unwrap_or(&"public, max-age=3600".to_string())
591 .clone();
592
593 let etag = headers.get("etag").cloned();
594
595 let last_modified = headers.get("last-modified").cloned();
596
597 let entry = CacheEntry { body, content_type, cache_control, etag, last_modified };
598
599 set_cached(&path, entry);
600
601 dev_log!("lifecycle", "[Scheme] Cached response for: {}", path);
602 }
603
604 response.unwrap_or_else(|_| build_error_response(500, "Internal Server Error"))
605 },
606
607 Err(e) => {
608 dev_log!("lifecycle", "error: [Scheme] Failed to forward request: {}", e);
609
610 build_error_response(503, &format!("Service Unavailable: {}", e))
611 },
612 }
613}
614
615fn build_error_response(status:u16, message:&str) -> Response<Vec<u8>> {
617 let body = serde_json::json!({
618 "error": message,
619 "status": status
620 });
621
622 Builder::new()
623 .status(status)
624 .header("Content-Type", "application/json")
625 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
626 .body(serde_json::to_vec(&body).unwrap_or_default())
627 .unwrap_or_else(|_| Builder::new().status(500).body(Vec::new()).unwrap())
628}
629
630fn build_cors_preflight_response() -> Response<Vec<u8>> {
632 Builder::new()
633 .status(204)
634 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
635 .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
636 .header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
637 .header("Access-Control-Max-Age", "86400")
638 .body(Vec::new())
639 .unwrap()
640}
641
642fn build_cached_response(entry:CacheEntry) -> Response<Vec<u8>> {
644 let mut builder = Builder::new()
645 .status(200)
646 .header("Content-Type", &entry.content_type)
647 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
648 .header("Cache-Control", &entry.cache_control);
649
650 if let Some(etag) = &entry.etag {
651 builder = builder.header("ETag", etag);
652 }
653
654 if let Some(last_modified) = &entry.last_modified {
655 builder = builder.header("Last-Modified", last_modified);
656 }
657
658 builder
659 .body(entry.body)
660 .unwrap_or_else(|_| build_error_response(500, "Internal Server Error"))
661}
662
663pub fn register_land_service(name:&str, port:u16) {
672 let registry = get_service_registry().expect("Service registry not initialized. Call init_service_registry first.");
673
674 registry.register(name.to_string(), port, Some("/health".to_string()));
675
676 dev_log!("lifecycle", "[Scheme] Registered service: {} -> {}", name, port);
677}
678
679pub fn get_land_port(name:&str) -> Option<u16> {
690 let registry = get_service_registry()?;
691
692 registry.lookup(name).map(|s| s.port)
693}
694
695pub fn land_scheme_handler_async<R:tauri::Runtime>(
728 _ctx:tauri::UriSchemeContext<'_, R>,
729
730 request:tauri::http::request::Request<Vec<u8>>,
731
732 responder:tauri::UriSchemeResponder,
733) {
734 std::thread::spawn(move || {
736 let response = land_scheme_handler(&request);
737 responder.respond(response);
738 });
739}
740
741#[allow(dead_code)]
750fn get_cors_origins() -> &'static str {
751 "land://localhost, http://land.localhost, land://code.land.playform.cloud"
753}
754
755#[inline]
760pub fn Scheme() {}
761
762fn MimeFromExtension(Path:&str) -> &'static str {
768 if Path.ends_with(".js") || Path.ends_with(".mjs") {
769 "application/javascript"
770 } else if Path.ends_with(".css") {
771 "text/css"
772 } else if Path.ends_with(".html") || Path.ends_with(".htm") {
773 "text/html"
774 } else if Path.ends_with(".json") {
775 "application/json"
776 } else if Path.ends_with(".svg") {
777 "image/svg+xml"
778 } else if Path.ends_with(".png") {
779 "image/png"
780 } else if Path.ends_with(".jpg") || Path.ends_with(".jpeg") {
781 "image/jpeg"
782 } else if Path.ends_with(".gif") {
783 "image/gif"
784 } else if Path.ends_with(".woff") {
785 "font/woff"
786 } else if Path.ends_with(".woff2") {
787 "font/woff2"
788 } else if Path.ends_with(".ttf") {
789 "font/ttf"
790 } else if Path.ends_with(".wasm") {
791 "application/wasm"
792 } else if Path.ends_with(".map") {
793 "application/json"
794 } else if Path.ends_with(".txt") || Path.ends_with(".md") {
795 "text/plain"
796 } else if Path.ends_with(".xml") {
797 "application/xml"
798 } else {
799 "application/octet-stream"
800 }
801}
802
803pub fn VscodeFileSchemeHandler<R:tauri::Runtime>(
832 AppHandle:&tauri::AppHandle<R>,
833
834 Request:&tauri::http::request::Request<Vec<u8>>,
835) -> Response<Vec<u8>> {
836 let Result = catch_unwind(AssertUnwindSafe(|| _VscodeFileSchemeHandler(AppHandle, Request)));
842
843 match Result {
844 Ok(Response) => Response,
845
846 Err(Panic) => {
847 let Info = if let Some(Text) = Panic.downcast_ref::<&str>() {
848 Text.to_string()
849 } else if let Some(Text) = Panic.downcast_ref::<String>() {
850 Text.clone()
851 } else {
852 "unknown panic".to_string()
853 };
854
855 dev_log!(
856 "lifecycle",
857 "error: [LandFix:VscodeFile] caught panic in scheme handler: {}",
858 Info
859 );
860
861 build_error_response(500, &format!("Internal Server Error (caught panic: {})", Info))
862 },
863 }
864}
865
866fn _VscodeFileSchemeHandler<R:tauri::Runtime>(
867 AppHandle:&tauri::AppHandle<R>,
868
869 Request:&tauri::http::request::Request<Vec<u8>>,
870) -> Response<Vec<u8>> {
871 let Uri = Request.uri().to_string();
872
873 dev_log!("scheme-assets", "[LandFix:VscodeFile] Request: {}", Uri);
879
880 dev_log!("scheme-assets", "[SchemeAssets] request uri={}", Uri);
881
882 let FilePath = Uri
897 .strip_prefix("vscode-file://vscode-app/")
898 .or_else(|| Uri.strip_prefix("vscode-file://vscode-app"))
899 .or_else(|| {
900 let After = Uri.strip_prefix("vscode-file://")?;
903 let SlashIdx = After.find('/')?;
904 Some(&After[SlashIdx + 1..])
905 })
906 .unwrap_or("");
907
908 let CleanPath = if FilePath.starts_with("Static/Application//out/") {
911 FilePath.replacen("Static/Application//out/", "Static/Application/", 1)
912 } else if FilePath.starts_with("Static/Application/out/") {
913 FilePath.replacen("Static/Application/out/", "Static/Application/", 1)
914 } else {
915 FilePath.to_string()
916 };
917
918 let CleanPath = if CleanPath.starts_with("Static/node_modules/") {
922 CleanPath.replacen("Static/node_modules/", "Static/Application/node_modules/", 1)
923 } else {
924 CleanPath
925 };
926
927 let CleanPath = match CleanPath.split_once(['?', '#']) {
940 Some((Before, _)) => Before.to_string(),
941
942 None => CleanPath,
943 };
944
945 if CleanPath.ends_with(".map") {
953 return Builder::new()
954 .status(204)
955 .header("Access-Control-Allow-Origin", "*")
956 .header("Cross-Origin-Resource-Policy", "cross-origin")
957 .body(Vec::new())
958 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
959 }
960
961 if CleanPath.ends_with(".css") && CleanPath.starts_with("Static/Application/") {
989 let LocalPath = format!("/Static/Application/{}", CleanPath.trim_start_matches("Static/Application/"));
990
991 let Body = format!("globalThis._LOAD_CSS_WORKER?.({:?}); export default {{}};", LocalPath);
992
993 dev_log!(
994 "scheme-assets",
995 "[LandFix:VscodeFile] css-shim {} -> _LOAD_CSS_WORKER({})",
996 CleanPath,
997 LocalPath
998 );
999
1000 return Builder::new()
1001 .status(200)
1002 .header("Content-Type", "application/javascript; charset=utf-8")
1003 .header("Access-Control-Allow-Origin", "*")
1004 .header("Cross-Origin-Resource-Policy", "cross-origin")
1005 .header("Cross-Origin-Embedder-Policy", "require-corp")
1006 .header("Cache-Control", "public, max-age=31536000, immutable")
1007 .body(Body.into_bytes())
1008 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1009 }
1010
1011 let IsAbsoluteOSPath = [
1019 "Volumes/",
1020 "Users/",
1021 "Library/",
1022 "System/",
1023 "Applications/",
1024 "private/",
1025 "tmp/",
1026 "var/",
1027 "etc/",
1028 "opt/",
1029 "home/",
1030 "usr/",
1031 "srv/",
1032 "mnt/",
1033 "root/",
1034 ]
1035 .iter()
1036 .any(|Prefix| CleanPath.starts_with(Prefix));
1037
1038 if IsAbsoluteOSPath {
1039 let AbsolutePath = format!("/{}", CleanPath);
1040
1041 let FilesystemPath = std::path::Path::new(&AbsolutePath);
1042
1043 dev_log!(
1044 "scheme-assets",
1045 "[LandFix:VscodeFile] os-abs candidate {} (exists={}, is_file={})",
1046 AbsolutePath,
1047 FilesystemPath.exists(),
1048 FilesystemPath.is_file()
1049 );
1050
1051 if FilesystemPath.exists() && FilesystemPath.is_file() {
1052 match crate::Cache::AssetMemoryMap::LoadOrInsert::Fn(FilesystemPath) {
1058 Ok(Entry) => {
1059 let AcceptsBrotli = Request
1060 .headers()
1061 .get("accept-encoding")
1062 .and_then(|V| V.to_str().ok())
1063 .map(|S| S.contains("br"))
1064 .unwrap_or(false);
1065
1066 let (Body, Encoding):(Vec<u8>, Option<&str>) = if AcceptsBrotli {
1067 match Entry.AsBrotliSlice() {
1068 Some(Slice) => (Slice.to_vec(), Some("br")),
1069
1070 None => (Entry.AsSlice().to_vec(), None),
1071 }
1072 } else {
1073 (Entry.AsSlice().to_vec(), None)
1074 };
1075
1076 dev_log!(
1077 "scheme-assets",
1078 "[LandFix:VscodeFile] os-abs served {} ({}, {} bytes, encoding={:?})",
1079 AbsolutePath,
1080 Entry.Mime,
1081 Body.len(),
1082 Encoding
1083 );
1084
1085 let mut B = Builder::new()
1095 .status(200)
1096 .header("Content-Type", Entry.Mime)
1097 .header("Access-Control-Allow-Origin", "*")
1098 .header("Cross-Origin-Resource-Policy", "cross-origin")
1099 .header("Cross-Origin-Embedder-Policy", "require-corp")
1100 .header("Cache-Control", "public, max-age=3600");
1101
1102 if let Some(Enc) = Encoding {
1103 B = B.header("Content-Encoding", Enc);
1104 }
1105
1106 return B
1107 .body(Body)
1108 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1109 },
1110
1111 Err(Error) => {
1112 dev_log!(
1113 "lifecycle",
1114 "warn: [LandFix:VscodeFile] os-abs mmap failure {}: {}",
1115 AbsolutePath,
1116 Error
1117 );
1118 },
1119 }
1120 } else {
1121 dev_log!("lifecycle", "warn: [LandFix:VscodeFile] os-abs not on disk: {}", AbsolutePath);
1122 }
1123 }
1124
1125 dev_log!("lifecycle", "[LandFix:VscodeFile] Resolved path: {}", CleanPath);
1126
1127 let AssetResult = AppHandle.asset_resolver().get(CleanPath.clone());
1131
1132 if let Some(Asset) = AssetResult {
1133 let Mime = MimeFromExtension(&CleanPath);
1134
1135 dev_log!(
1136 "lifecycle",
1137 "[LandFix:VscodeFile] Serving (embedded) {} ({}, {} bytes)",
1138 CleanPath,
1139 Mime,
1140 Asset.bytes.len()
1141 );
1142
1143 dev_log!(
1144 "scheme-assets",
1145 "[SchemeAssets] serve source=embedded path={} mime={} bytes={}",
1146 CleanPath,
1147 Mime,
1148 Asset.bytes.len()
1149 );
1150
1151 return Builder::new()
1152 .status(200)
1153 .header("Content-Type", Mime)
1154 .header("Access-Control-Allow-Origin", "*")
1155 .header("Cross-Origin-Resource-Policy", "cross-origin")
1156 .header("Cross-Origin-Embedder-Policy", "require-corp")
1157 .header("Cache-Control", "public, max-age=31536000, immutable")
1158 .body(Asset.bytes.to_vec())
1159 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1160 }
1161
1162 let StaticRoot = crate::IPC::WindServiceHandlers::Utilities::ApplicationRoot::get_static_application_root();
1164
1165 if let Some(Root) = StaticRoot {
1166 let FilesystemPath = std::path::Path::new(&Root).join(&CleanPath);
1167
1168 if FilesystemPath.exists() && FilesystemPath.is_file() {
1169 match crate::Cache::AssetMemoryMap::LoadOrInsert::Fn(&FilesystemPath) {
1173 Ok(Entry) => {
1174 let AcceptsBrotli = Request
1175 .headers()
1176 .get("accept-encoding")
1177 .and_then(|V| V.to_str().ok())
1178 .map(|S| S.contains("br"))
1179 .unwrap_or(false);
1180
1181 let (Body, Encoding):(Vec<u8>, Option<&str>) = if AcceptsBrotli {
1182 match Entry.AsBrotliSlice() {
1183 Some(Slice) => (Slice.to_vec(), Some("br")),
1184
1185 None => (Entry.AsSlice().to_vec(), None),
1186 }
1187 } else {
1188 (Entry.AsSlice().to_vec(), None)
1189 };
1190
1191 dev_log!(
1192 "lifecycle",
1193 "[LandFix:VscodeFile] Serving (fs-mmap) {} ({}, {} bytes, encoding={:?})",
1194 CleanPath,
1195 Entry.Mime,
1196 Body.len(),
1197 Encoding
1198 );
1199
1200 let mut B = Builder::new()
1210 .status(200)
1211 .header("Content-Type", Entry.Mime)
1212 .header("Access-Control-Allow-Origin", "*")
1213 .header("Cross-Origin-Resource-Policy", "cross-origin")
1214 .header("Cross-Origin-Embedder-Policy", "require-corp")
1215 .header("Cache-Control", "public, max-age=3600");
1216
1217 if let Some(Enc) = Encoding {
1218 B = B.header("Content-Encoding", Enc);
1219 }
1220
1221 return B
1222 .body(Body)
1223 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1224 },
1225
1226 Err(Error) => {
1227 dev_log!(
1228 "lifecycle",
1229 "warn: [LandFix:VscodeFile] Failed to read {}: {}",
1230 FilesystemPath.display(),
1231 Error
1232 );
1233 },
1234 }
1235 }
1236 }
1237
1238 dev_log!(
1239 "lifecycle",
1240 "warn: [LandFix:VscodeFile] Not found: {} (resolved: {})",
1241 Uri,
1242 CleanPath
1243 );
1244
1245 build_error_response(404, &format!("Not Found: {}", CleanPath))
1246}
1247
1248pub fn VscodeWebviewSchemeHandler<R:tauri::Runtime>(
1301 AppHandle:&tauri::AppHandle<R>,
1302
1303 Request:&tauri::http::request::Request<Vec<u8>>,
1304) -> Response<Vec<u8>> {
1305 let Result = catch_unwind(AssertUnwindSafe(|| _VscodeWebviewSchemeHandler(AppHandle, Request)));
1306
1307 match Result {
1308 Ok(Response) => Response,
1309
1310 Err(Panic) => {
1311 let Info = if let Some(Text) = Panic.downcast_ref::<&str>() {
1312 Text.to_string()
1313 } else if let Some(Text) = Panic.downcast_ref::<String>() {
1314 Text.clone()
1315 } else {
1316 "unknown panic".to_string()
1317 };
1318
1319 dev_log!(
1320 "lifecycle",
1321 "error: [LandFix:VscodeWebview] caught panic in scheme handler: {}",
1322 Info
1323 );
1324
1325 build_error_response(500, &format!("Internal Server Error (caught panic: {})", Info))
1326 },
1327 }
1328}
1329
1330fn _VscodeWebviewSchemeHandler<R:tauri::Runtime>(
1331 AppHandle:&tauri::AppHandle<R>,
1332
1333 Request:&tauri::http::request::Request<Vec<u8>>,
1334) -> Response<Vec<u8>> {
1335 let Uri = Request.uri().to_string();
1336
1337 dev_log!("scheme-assets", "[LandFix:VscodeWebview] Request: {}", Uri);
1338
1339 let After = match Uri.strip_prefix("vscode-webview://") {
1344 Some(Rest) => Rest,
1345
1346 None => {
1347 return build_error_response(400, "vscode-webview scheme without prefix");
1348 },
1349 };
1350
1351 let PathStart = match After.find('/') {
1352 Some(Index) => Index + 1,
1353
1354 None => {
1355 return build_error_response(400, "vscode-webview URI missing path component");
1356 },
1357 };
1358
1359 let PathPlusQuery = &After[PathStart..];
1360
1361 let CleanPath:&str = PathPlusQuery
1363 .split_once(|C:char| C == '?' || C == '#')
1364 .map(|(Path, _)| Path)
1365 .unwrap_or(PathPlusQuery);
1366
1367 if CleanPath.is_empty() || CleanPath.contains("..") {
1371 return build_error_response(404, "vscode-webview path empty or traversal");
1372 }
1373
1374 let ResolvedPath = format!("Static/Application/vs/workbench/contrib/webview/browser/pre/{}", CleanPath);
1375
1376 dev_log!(
1377 "scheme-assets",
1378 "[LandFix:VscodeWebview] resolve {} -> {}",
1379 CleanPath,
1380 ResolvedPath
1381 );
1382
1383 if let Some(Asset) = AppHandle.asset_resolver().get(ResolvedPath.clone()) {
1388 let Mime = MimeFromExtension(&ResolvedPath);
1389
1390 dev_log!(
1391 "scheme-assets",
1392 "[LandFix:VscodeWebview] serve embedded {} ({}, {} bytes)",
1393 ResolvedPath,
1394 Mime,
1395 Asset.bytes.len()
1396 );
1397
1398 return Builder::new()
1399 .status(200)
1400 .header("Content-Type", Mime)
1401 .header("Access-Control-Allow-Origin", "*")
1402 .header("Cross-Origin-Embedder-Policy", "require-corp")
1403 .header("Cross-Origin-Resource-Policy", "cross-origin")
1404 .header("Cache-Control", "no-cache")
1405 .body(Asset.bytes.to_vec())
1406 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1407 }
1408
1409 let StaticRoot = crate::IPC::WindServiceHandlers::Utilities::ApplicationRoot::get_static_application_root();
1414
1415 if let Some(Root) = StaticRoot {
1416 let FilesystemPath = std::path::Path::new(&Root).join(&ResolvedPath);
1417
1418 if FilesystemPath.exists() && FilesystemPath.is_file() {
1419 match std::fs::read(&FilesystemPath) {
1420 Ok(Bytes) => {
1421 let Mime = MimeFromExtension(&ResolvedPath);
1422
1423 dev_log!(
1424 "scheme-assets",
1425 "[LandFix:VscodeWebview] serve filesystem {} ({}, {} bytes)",
1426 FilesystemPath.display(),
1427 Mime,
1428 Bytes.len()
1429 );
1430
1431 return Builder::new()
1432 .status(200)
1433 .header("Content-Type", Mime)
1434 .header("Access-Control-Allow-Origin", "*")
1435 .header("Cross-Origin-Embedder-Policy", "require-corp")
1436 .header("Cross-Origin-Resource-Policy", "cross-origin")
1437 .header("Cache-Control", "no-cache")
1438 .body(Bytes)
1439 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1440 },
1441
1442 Err(Error) => {
1443 dev_log!(
1444 "lifecycle",
1445 "warn: [LandFix:VscodeWebview] Failed to read {}: {}",
1446 FilesystemPath.display(),
1447 Error
1448 );
1449 },
1450 }
1451 }
1452 }
1453
1454 dev_log!(
1455 "lifecycle",
1456 "warn: [LandFix:VscodeWebview] Not found: {} (resolved: {})",
1457 Uri,
1458 ResolvedPath
1459 );
1460
1461 build_error_response(404, &format!("Not Found: {}", ResolvedPath))
1462}