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
243 let hop_by_hop_headers = [
244 "connection",
245 "keep-alive",
246 "proxy-authenticate",
247 "proxy-authorization",
248 "te",
249 "trailers",
250 "transfer-encoding",
251 "upgrade",
252 ];
253
254 if !hop_by_hop_headers.contains(&header_name.as_str()) {
255 value.to_str().ok().map(|v| (name.as_str().to_string(), v.to_string()))
256 } else {
257 None
258 }
259 })
260 .collect();
261
262 let result = std::thread::spawn(move || {
264 let rt = tokio::runtime::Runtime::new().map_err(|e| format!("Failed to create runtime: {}", e))?;
265
266 rt.block_on(async {
267 use tokio::{
268 io::{AsyncReadExt, AsyncWriteExt},
269 net::TcpStream,
270 };
271
272 let mut stream = TcpStream::connect(&addr)
274 .await
275 .map_err(|e| format!("Failed to connect: {}", e))?;
276
277 let mut request_str = format!("{} {} HTTP/1.1\r\nHost: {}\r\n", method.as_str(), path, host);
279
280 for (name, value) in &headers {
282 request_str.push_str(&format!("{}: {}\r\n", name, value));
283 }
284
285 if !body.is_empty() {
287 request_str.push_str(&format!("Content-Length: {}\r\n", body.len()));
288 }
289
290 request_str.push_str("\r\n");
291
292 stream
294 .write_all(request_str.as_bytes())
295 .await
296 .map_err(|e| format!("Failed to write request: {}", e))?;
297
298 if !body.is_empty() {
299 stream
300 .write_all(&body)
301 .await
302 .map_err(|e| format!("Failed to write body: {}", e))?;
303 }
304
305 let mut buffer = Vec::new();
307
308 let mut temp_buf = [0u8; 8192];
309
310 loop {
311 let n = stream
312 .read(&mut temp_buf)
313 .await
314 .map_err(|e| format!("Failed to read response: {}", e))?;
315
316 if n == 0 {
317 break;
318 }
319
320 buffer.extend_from_slice(&temp_buf[..n]);
321
322 if buffer.len() > 1024 * 1024 {
325 dev_log!("lifecycle", "warn: [Scheme] Response too large, truncating");
327
328 break;
329 }
330
331 if let Some(headers_end) = buffer.windows(4).position(|w| w == b"\r\n\r\n") {
334 let headers = String::from_utf8_lossy(&buffer[..headers_end]);
335
336 if let Some(cl_line) = headers.lines().find(|l| l.to_lowercase().starts_with("content-length:")) {
337 if let Ok(cl) = cl_line.trim_start_matches("content-length:").trim().parse::<usize>() {
338 let body_expected = headers_end + 4 + cl;
339
340 if buffer.len() >= body_expected {
341 break;
342 }
343 }
344 } else if !headers.contains("Transfer-Encoding: chunked") {
345 continue;
347 }
348 }
349 }
350
351 parse_http_response(&buffer)
354 })
355 })
356 .join()
357 .map_err(|e| format!("Thread panicked: {:?}", e))?;
358
359 result
360}
361
362fn parse_http_response(response:&[u8]) -> Result<(u16, Vec<u8>, HashMap<String, String>), String> {
367 let headers_end = response
368 .windows(4)
369 .position(|w| w == b"\r\n\r\n")
370 .ok_or("Invalid HTTP response: no headers/body separator")?;
371
372 let headers_str =
373 std::str::from_utf8(&response[..headers_end]).map_err(|e| format!("Invalid UTF-8 in HTTP headers: {}", e))?;
374
375 let body = response[headers_end + 4..].to_vec();
376
377 let mut lines = headers_str.lines();
379
380 let status_line = lines.next().ok_or("Invalid HTTP response: no status line")?;
381
382 let status = status_line
384 .split_whitespace()
385 .nth(1)
386 .and_then(|s| s.parse::<u16>().ok())
387 .ok_or_else(|| format!("Invalid status line: {}", status_line))?;
388
389 let mut headers = HashMap::new();
391
392 for line in lines {
393 if let Some((name, value)) = line.split_once(':') {
394 headers.insert(name.trim().to_lowercase(), value.trim().to_string());
395 }
396 }
397
398 Ok((status, body, headers))
399}
400
401pub fn land_scheme_handler(request:&Request<Vec<u8>>) -> Response<Vec<u8>> {
441 init_cache();
443
444 let uri = request.uri().to_string();
446
447 dev_log!("lifecycle", "[Scheme] Handling land:// request: {}", uri);
448
449 let (domain, path) = match parse_land_uri(&uri) {
451 Ok(result) => result,
452
453 Err(e) => {
454 dev_log!("lifecycle", "error: [Scheme] Failed to parse URI: {}", e);
455
456 return build_error_response(400, &format!("Bad Request: {}", e));
457 },
458 };
459
460 if request.method() == Method::OPTIONS {
462 dev_log!("lifecycle", "[Scheme] Handling CORS preflight request");
463
464 return build_cors_preflight_response();
465 }
466
467 if should_cache(&path) {
469 if let Some(cached) = get_cached(&path) {
470 dev_log!("lifecycle", "[Scheme] Cache hit for: {}", path);
471
472 return build_cached_response(cached);
473 }
474 }
475
476 let registry = match get_service_registry() {
478 Some(r) => r,
479
480 None => {
481 dev_log!("lifecycle", "error: [Scheme] Service registry not initialized");
482
483 return build_error_response(503, "Service Unavailable: Registry not initialized");
484 },
485 };
486
487 let service = match registry.lookup(&domain) {
488 Some(s) => s,
489
490 None => {
491 dev_log!("lifecycle", "warn: [Scheme] Service not found: {}", domain);
492
493 return build_error_response(404, &format!("Not Found: Service {} not registered", domain));
494 },
495 };
496
497 let local_url = format!("http://127.0.0.1:{}{}", service.port, path);
499
500 dev_log!(
501 "lifecycle",
502 "[Scheme] Routing {} {} to local service at {}",
503 request.method(),
504 uri,
505 local_url
506 );
507
508 let result = forward_http_request(&local_url, request, request.method().clone());
510
511 match result {
512 Ok((status, body, headers)) => {
513 let body_bytes = body.clone();
515
516 let LowerPath = path.to_ascii_lowercase();
527
528 let IsAssetRequest = LowerPath.ends_with(".js")
529 || LowerPath.ends_with(".mjs")
530 || LowerPath.ends_with(".cjs")
531 || LowerPath.ends_with(".json")
532 || LowerPath.ends_with(".map")
533 || LowerPath.ends_with(".css")
534 || LowerPath.ends_with(".wasm")
535 || LowerPath.ends_with(".svg")
536 || LowerPath.ends_with(".png")
537 || LowerPath.ends_with(".woff")
538 || LowerPath.ends_with(".woff2")
539 || LowerPath.ends_with(".ttf")
540 || LowerPath.ends_with(".otf");
541
542 let UpstreamSaysHtml = headers
543 .get("content-type")
544 .map(|V| V.to_ascii_lowercase().contains("text/html"))
545 .unwrap_or(false);
546
547 if IsAssetRequest && (status == 404 || (status >= 400 && UpstreamSaysHtml)) {
548 dev_log!(
549 "scheme-assets",
550 "[LandFix:Mime] swap HTML 404 → text/plain empty for asset path={} status={}",
551 path,
552 status
553 );
554
555 return Builder::new()
556 .status(404)
557 .header("Content-Type", "text/plain; charset=utf-8")
558 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
559 .body(Vec::<u8>::new())
560 .unwrap_or_else(|_| build_error_response(500, "Failed to build 404 response"));
561 }
562
563 let mut response_builder = Builder::new()
565 .status(status)
566 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
567 .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
568 .header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
569
570 let important_headers = [
572 "content-type",
573 "content-length",
574 "etag",
575 "last-modified",
576 "cache-control",
577 "expires",
578 "content-encoding",
579 "content-disposition",
580 "location",
581 ];
582
583 for header_name in &important_headers {
584 if let Some(value) = headers.get(*header_name) {
585 response_builder = response_builder.header(*header_name, value);
586 }
587 }
588
589 let response = response_builder.body(body_bytes);
590
591 if status == 200 && should_cache(&path) {
593 let content_type = headers
594 .get("content-type")
595 .unwrap_or(&"application/octet-stream".to_string())
596 .clone();
597
598 let cache_control = headers
599 .get("cache-control")
600 .unwrap_or(&"public, max-age=3600".to_string())
601 .clone();
602
603 let etag = headers.get("etag").cloned();
604
605 let last_modified = headers.get("last-modified").cloned();
606
607 let entry = CacheEntry { body, content_type, cache_control, etag, last_modified };
608
609 set_cached(&path, entry);
610
611 dev_log!("lifecycle", "[Scheme] Cached response for: {}", path);
612 }
613
614 response.unwrap_or_else(|_| build_error_response(500, "Internal Server Error"))
615 },
616
617 Err(e) => {
618 dev_log!("lifecycle", "error: [Scheme] Failed to forward request: {}", e);
619
620 build_error_response(503, &format!("Service Unavailable: {}", e))
621 },
622 }
623}
624
625fn build_error_response(status:u16, message:&str) -> Response<Vec<u8>> {
627 let body = serde_json::json!({
628 "error": message,
629 "status": status
630 });
631
632 Builder::new()
633 .status(status)
634 .header("Content-Type", "application/json")
635 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
636 .body(serde_json::to_vec(&body).unwrap_or_default())
637 .unwrap_or_else(|_| Builder::new().status(500).body(Vec::new()).unwrap())
638}
639
640fn build_cors_preflight_response() -> Response<Vec<u8>> {
642 Builder::new()
643 .status(204)
644 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
645 .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
646 .header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
647 .header("Access-Control-Max-Age", "86400")
648 .body(Vec::new())
649 .unwrap()
650}
651
652fn build_cached_response(entry:CacheEntry) -> Response<Vec<u8>> {
654 let mut builder = Builder::new()
655 .status(200)
656 .header("Content-Type", &entry.content_type)
657 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
658 .header("Cache-Control", &entry.cache_control);
659
660 if let Some(etag) = &entry.etag {
661 builder = builder.header("ETag", etag);
662 }
663
664 if let Some(last_modified) = &entry.last_modified {
665 builder = builder.header("Last-Modified", last_modified);
666 }
667
668 builder
669 .body(entry.body)
670 .unwrap_or_else(|_| build_error_response(500, "Internal Server Error"))
671}
672
673pub fn register_land_service(name:&str, port:u16) {
682 let registry = get_service_registry().expect("Service registry not initialized. Call init_service_registry first.");
683
684 registry.register(name.to_string(), port, Some("/health".to_string()));
685
686 dev_log!("lifecycle", "[Scheme] Registered service: {} -> {}", name, port);
687}
688
689pub fn get_land_port(name:&str) -> Option<u16> {
700 let registry = get_service_registry()?;
701
702 registry.lookup(name).map(|s| s.port)
703}
704
705pub fn land_scheme_handler_async<R:tauri::Runtime>(
738 _ctx:tauri::UriSchemeContext<'_, R>,
739
740 request:tauri::http::request::Request<Vec<u8>>,
741
742 responder:tauri::UriSchemeResponder,
743) {
744 std::thread::spawn(move || {
746 let response = land_scheme_handler(&request);
747
748 responder.respond(response);
749 });
750}
751
752fn get_cors_origins() -> &'static str {
761 "land://localhost, http://land.localhost, land://code.land.playform.cloud"
763}
764
765#[inline]
770pub fn Scheme() {}
771
772fn MimeFromExtension(Path:&str) -> &'static str {
778 if Path.ends_with(".js") || Path.ends_with(".mjs") {
779 "application/javascript"
780 } else if Path.ends_with(".css") {
781 "text/css"
782 } else if Path.ends_with(".html") || Path.ends_with(".htm") {
783 "text/html"
784 } else if Path.ends_with(".json") {
785 "application/json"
786 } else if Path.ends_with(".svg") {
787 "image/svg+xml"
788 } else if Path.ends_with(".png") {
789 "image/png"
790 } else if Path.ends_with(".jpg") || Path.ends_with(".jpeg") {
791 "image/jpeg"
792 } else if Path.ends_with(".gif") {
793 "image/gif"
794 } else if Path.ends_with(".woff") {
795 "font/woff"
796 } else if Path.ends_with(".woff2") {
797 "font/woff2"
798 } else if Path.ends_with(".ttf") {
799 "font/ttf"
800 } else if Path.ends_with(".wasm") {
801 "application/wasm"
802 } else if Path.ends_with(".map") {
803 "application/json"
804 } else if Path.ends_with(".txt") || Path.ends_with(".md") {
805 "text/plain"
806 } else if Path.ends_with(".xml") {
807 "application/xml"
808 } else {
809 "application/octet-stream"
810 }
811}
812
813pub fn VscodeFileSchemeHandler<R:tauri::Runtime>(
842 AppHandle:&tauri::AppHandle<R>,
843
844 Request:&tauri::http::request::Request<Vec<u8>>,
845) -> Response<Vec<u8>> {
846 let Result = catch_unwind(AssertUnwindSafe(|| _VscodeFileSchemeHandler(AppHandle, Request)));
852
853 match Result {
854 Ok(Response) => Response,
855
856 Err(Panic) => {
857 let Info = if let Some(Text) = Panic.downcast_ref::<&str>() {
858 Text.to_string()
859 } else if let Some(Text) = Panic.downcast_ref::<String>() {
860 Text.clone()
861 } else {
862 "unknown panic".to_string()
863 };
864
865 dev_log!(
866 "lifecycle",
867 "error: [LandFix:VscodeFile] caught panic in scheme handler: {}",
868 Info
869 );
870
871 build_error_response(500, &format!("Internal Server Error (caught panic: {})", Info))
872 },
873 }
874}
875
876fn _VscodeFileSchemeHandler<R:tauri::Runtime>(
877 AppHandle:&tauri::AppHandle<R>,
878
879 Request:&tauri::http::request::Request<Vec<u8>>,
880) -> Response<Vec<u8>> {
881 let Uri = Request.uri().to_string();
882
883 dev_log!("scheme-assets", "[LandFix:VscodeFile] Request: {}", Uri);
889
890 dev_log!("scheme-assets", "[SchemeAssets] request uri={}", Uri);
891
892 let FilePath = Uri
907 .strip_prefix("vscode-file://vscode-app/")
908 .or_else(|| Uri.strip_prefix("vscode-file://vscode-app"))
909 .or_else(|| {
910 let After = Uri.strip_prefix("vscode-file://")?;
913
914 let SlashIdx = After.find('/')?;
915
916 Some(&After[SlashIdx + 1..])
917 })
918 .unwrap_or("");
919
920 let CleanPath = if FilePath.starts_with("Static/Application//out/") {
923 FilePath.replacen("Static/Application//out/", "Static/Application/", 1)
924 } else if FilePath.starts_with("Static/Application/out/") {
925 FilePath.replacen("Static/Application/out/", "Static/Application/", 1)
926 } else {
927 FilePath.to_string()
928 };
929
930 let CleanPath = if CleanPath.starts_with("Static/node_modules/") {
934 CleanPath.replacen("Static/node_modules/", "Static/Application/node_modules/", 1)
935 } else {
936 CleanPath
937 };
938
939 let CleanPath = match CleanPath.split_once(['?', '#']) {
952 Some((Before, _)) => Before.to_string(),
953
954 None => CleanPath,
955 };
956
957 if CleanPath.ends_with(".map") {
965 return Builder::new()
966 .status(204)
967 .header("Access-Control-Allow-Origin", "*")
968 .header("Cross-Origin-Resource-Policy", "cross-origin")
969 .body(Vec::new())
970 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
971 }
972
973 if CleanPath.ends_with(".css") && CleanPath.starts_with("Static/Application/") {
1001 let LocalPath = format!("/Static/Application/{}", CleanPath.trim_start_matches("Static/Application/"));
1002
1003 let Body = format!("globalThis._LOAD_CSS_WORKER?.({:?}); export default {{}};", LocalPath);
1004
1005 dev_log!(
1006 "scheme-assets",
1007 "[LandFix:VscodeFile] css-shim {} -> _LOAD_CSS_WORKER({})",
1008 CleanPath,
1009 LocalPath
1010 );
1011
1012 return Builder::new()
1013 .status(200)
1014 .header("Content-Type", "application/javascript; charset=utf-8")
1015 .header("Access-Control-Allow-Origin", "*")
1016 .header("Cross-Origin-Resource-Policy", "cross-origin")
1017 .header("Cross-Origin-Embedder-Policy", "require-corp")
1018 .header("Cache-Control", "public, max-age=31536000, immutable")
1019 .body(Body.into_bytes())
1020 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1021 }
1022
1023 let IsAbsoluteOSPath = [
1031 "Volumes/",
1032 "Users/",
1033 "Library/",
1034 "System/",
1035 "Applications/",
1036 "private/",
1037 "tmp/",
1038 "var/",
1039 "etc/",
1040 "opt/",
1041 "home/",
1042 "usr/",
1043 "srv/",
1044 "mnt/",
1045 "root/",
1046 ]
1047 .iter()
1048 .any(|Prefix| CleanPath.starts_with(Prefix));
1049
1050 if IsAbsoluteOSPath {
1051 let AbsolutePath = format!("/{}", CleanPath);
1052
1053 let FilesystemPath = std::path::Path::new(&AbsolutePath);
1054
1055 dev_log!(
1056 "scheme-assets",
1057 "[LandFix:VscodeFile] os-abs candidate {} (exists={}, is_file={})",
1058 AbsolutePath,
1059 FilesystemPath.exists(),
1060 FilesystemPath.is_file()
1061 );
1062
1063 if FilesystemPath.exists() && FilesystemPath.is_file() {
1064 match ::Cache::AssetMemoryMap::LoadOrInsert::Fn(FilesystemPath) {
1070 Ok(Entry) => {
1071 let AcceptsBrotli = Request
1072 .headers()
1073 .get("accept-encoding")
1074 .and_then(|V| V.to_str().ok())
1075 .map(|S| S.contains("br"))
1076 .unwrap_or(false);
1077
1078 let (Body, Encoding):(Vec<u8>, Option<&str>) = if AcceptsBrotli {
1079 match Entry.AsBrotliSlice() {
1080 Some(Slice) => (Slice.to_vec(), Some("br")),
1081
1082 None => (Entry.AsSlice().to_vec(), None),
1083 }
1084 } else {
1085 (Entry.AsSlice().to_vec(), None)
1086 };
1087
1088 dev_log!(
1089 "scheme-assets",
1090 "[LandFix:VscodeFile] os-abs served {} ({}, {} bytes, encoding={:?})",
1091 AbsolutePath,
1092 Entry.Mime,
1093 Body.len(),
1094 Encoding
1095 );
1096
1097 let mut B = Builder::new()
1107 .status(200)
1108 .header("Content-Type", Entry.Mime)
1109 .header("Access-Control-Allow-Origin", "*")
1110 .header("Cross-Origin-Resource-Policy", "cross-origin")
1111 .header("Cross-Origin-Embedder-Policy", "require-corp")
1112 .header("Cache-Control", "public, max-age=3600");
1113
1114 if let Some(Enc) = Encoding {
1115 B = B.header("Content-Encoding", Enc);
1116 }
1117
1118 return B
1119 .body(Body)
1120 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1121 },
1122
1123 Err(Error) => {
1124 dev_log!(
1125 "lifecycle",
1126 "warn: [LandFix:VscodeFile] os-abs mmap failure {}: {}",
1127 AbsolutePath,
1128 Error
1129 );
1130 },
1131 }
1132 } else {
1133 dev_log!("lifecycle", "warn: [LandFix:VscodeFile] os-abs not on disk: {}", AbsolutePath);
1134 }
1135 }
1136
1137 dev_log!("lifecycle", "[LandFix:VscodeFile] Resolved path: {}", CleanPath);
1138
1139 let AssetResult = AppHandle.asset_resolver().get(CleanPath.clone());
1143
1144 if let Some(Asset) = AssetResult {
1145 let Mime = MimeFromExtension(&CleanPath);
1146
1147 dev_log!(
1148 "lifecycle",
1149 "[LandFix:VscodeFile] Serving (embedded) {} ({}, {} bytes)",
1150 CleanPath,
1151 Mime,
1152 Asset.bytes.len()
1153 );
1154
1155 dev_log!(
1156 "scheme-assets",
1157 "[SchemeAssets] serve source=embedded path={} mime={} bytes={}",
1158 CleanPath,
1159 Mime,
1160 Asset.bytes.len()
1161 );
1162
1163 return Builder::new()
1164 .status(200)
1165 .header("Content-Type", Mime)
1166 .header("Access-Control-Allow-Origin", "*")
1167 .header("Cross-Origin-Resource-Policy", "cross-origin")
1168 .header("Cross-Origin-Embedder-Policy", "require-corp")
1169 .header("Cache-Control", "public, max-age=31536000, immutable")
1170 .body(Asset.bytes.to_vec())
1171 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1172 }
1173
1174 let StaticRoot = crate::IPC::WindServiceHandlers::Utilities::ApplicationRoot::Get::Fn();
1176
1177 if let Some(Root) = StaticRoot {
1178 let FilesystemPath = std::path::Path::new(&Root).join(&CleanPath);
1179
1180 if FilesystemPath.exists() && FilesystemPath.is_file() {
1181 match ::Cache::AssetMemoryMap::LoadOrInsert::Fn(&FilesystemPath) {
1185 Ok(Entry) => {
1186 let AcceptsBrotli = Request
1187 .headers()
1188 .get("accept-encoding")
1189 .and_then(|V| V.to_str().ok())
1190 .map(|S| S.contains("br"))
1191 .unwrap_or(false);
1192
1193 let (Body, Encoding):(Vec<u8>, Option<&str>) = if AcceptsBrotli {
1194 match Entry.AsBrotliSlice() {
1195 Some(Slice) => (Slice.to_vec(), Some("br")),
1196
1197 None => (Entry.AsSlice().to_vec(), None),
1198 }
1199 } else {
1200 (Entry.AsSlice().to_vec(), None)
1201 };
1202
1203 dev_log!(
1204 "lifecycle",
1205 "[LandFix:VscodeFile] Serving (fs-mmap) {} ({}, {} bytes, encoding={:?})",
1206 CleanPath,
1207 Entry.Mime,
1208 Body.len(),
1209 Encoding
1210 );
1211
1212 let mut B = Builder::new()
1222 .status(200)
1223 .header("Content-Type", Entry.Mime)
1224 .header("Access-Control-Allow-Origin", "*")
1225 .header("Cross-Origin-Resource-Policy", "cross-origin")
1226 .header("Cross-Origin-Embedder-Policy", "require-corp")
1227 .header("Cache-Control", "public, max-age=3600");
1228
1229 if let Some(Enc) = Encoding {
1230 B = B.header("Content-Encoding", Enc);
1231 }
1232
1233 return B
1234 .body(Body)
1235 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1236 },
1237
1238 Err(Error) => {
1239 dev_log!(
1240 "lifecycle",
1241 "warn: [LandFix:VscodeFile] Failed to read {}: {}",
1242 FilesystemPath.display(),
1243 Error
1244 );
1245 },
1246 }
1247 }
1248 }
1249
1250 dev_log!(
1251 "lifecycle",
1252 "warn: [LandFix:VscodeFile] Not found: {} (resolved: {})",
1253 Uri,
1254 CleanPath
1255 );
1256
1257 build_error_response(404, &format!("Not Found: {}", CleanPath))
1258}
1259
1260pub fn VscodeWebviewSchemeHandler<R:tauri::Runtime>(
1313 AppHandle:&tauri::AppHandle<R>,
1314
1315 Request:&tauri::http::request::Request<Vec<u8>>,
1316) -> Response<Vec<u8>> {
1317 let Result = catch_unwind(AssertUnwindSafe(|| _VscodeWebviewSchemeHandler(AppHandle, Request)));
1318
1319 match Result {
1320 Ok(Response) => Response,
1321
1322 Err(Panic) => {
1323 let Info = if let Some(Text) = Panic.downcast_ref::<&str>() {
1324 Text.to_string()
1325 } else if let Some(Text) = Panic.downcast_ref::<String>() {
1326 Text.clone()
1327 } else {
1328 "unknown panic".to_string()
1329 };
1330
1331 dev_log!(
1332 "lifecycle",
1333 "error: [LandFix:VscodeWebview] caught panic in scheme handler: {}",
1334 Info
1335 );
1336
1337 build_error_response(500, &format!("Internal Server Error (caught panic: {})", Info))
1338 },
1339 }
1340}
1341
1342fn _VscodeWebviewSchemeHandler<R:tauri::Runtime>(
1343 AppHandle:&tauri::AppHandle<R>,
1344
1345 Request:&tauri::http::request::Request<Vec<u8>>,
1346) -> Response<Vec<u8>> {
1347 let Uri = Request.uri().to_string();
1348
1349 dev_log!("scheme-assets", "[LandFix:VscodeWebview] Request: {}", Uri);
1350
1351 let After = match Uri.strip_prefix("vscode-webview://") {
1356 Some(Rest) => Rest,
1357
1358 None => {
1359 return build_error_response(400, "vscode-webview scheme without prefix");
1360 },
1361 };
1362
1363 let PathStart = match After.find('/') {
1364 Some(Index) => Index + 1,
1365
1366 None => {
1367 return build_error_response(400, "vscode-webview URI missing path component");
1368 },
1369 };
1370
1371 let PathPlusQuery = &After[PathStart..];
1372
1373 let CleanPath:&str = PathPlusQuery
1375 .split_once(|C:char| C == '?' || C == '#')
1376 .map(|(Path, _)| Path)
1377 .unwrap_or(PathPlusQuery);
1378
1379 if CleanPath.is_empty() || CleanPath.contains("..") {
1383 return build_error_response(404, "vscode-webview path empty or traversal");
1384 }
1385
1386 let ResolvedPath = format!("Static/Application/vs/workbench/contrib/webview/browser/pre/{}", CleanPath);
1387
1388 dev_log!(
1389 "scheme-assets",
1390 "[LandFix:VscodeWebview] resolve {} -> {}",
1391 CleanPath,
1392 ResolvedPath
1393 );
1394
1395 if let Some(Asset) = AppHandle.asset_resolver().get(ResolvedPath.clone()) {
1400 let Mime = MimeFromExtension(&ResolvedPath);
1401
1402 dev_log!(
1403 "scheme-assets",
1404 "[LandFix:VscodeWebview] serve embedded {} ({}, {} bytes)",
1405 ResolvedPath,
1406 Mime,
1407 Asset.bytes.len()
1408 );
1409
1410 return Builder::new()
1411 .status(200)
1412 .header("Content-Type", Mime)
1413 .header("Access-Control-Allow-Origin", "*")
1414 .header("Cross-Origin-Embedder-Policy", "require-corp")
1415 .header("Cross-Origin-Resource-Policy", "cross-origin")
1416 .header("Cache-Control", "no-cache")
1417 .body(Asset.bytes.to_vec())
1418 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1419 }
1420
1421 let StaticRoot = crate::IPC::WindServiceHandlers::Utilities::ApplicationRoot::Get::Fn();
1426
1427 if let Some(Root) = StaticRoot {
1428 let FilesystemPath = std::path::Path::new(&Root).join(&ResolvedPath);
1429
1430 if FilesystemPath.exists() && FilesystemPath.is_file() {
1431 match std::fs::read(&FilesystemPath) {
1432 Ok(Bytes) => {
1433 let Mime = MimeFromExtension(&ResolvedPath);
1434
1435 dev_log!(
1436 "scheme-assets",
1437 "[LandFix:VscodeWebview] serve filesystem {} ({}, {} bytes)",
1438 FilesystemPath.display(),
1439 Mime,
1440 Bytes.len()
1441 );
1442
1443 return Builder::new()
1444 .status(200)
1445 .header("Content-Type", Mime)
1446 .header("Access-Control-Allow-Origin", "*")
1447 .header("Cross-Origin-Embedder-Policy", "require-corp")
1448 .header("Cross-Origin-Resource-Policy", "cross-origin")
1449 .header("Cache-Control", "no-cache")
1450 .body(Bytes)
1451 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1452 },
1453
1454 Err(Error) => {
1455 dev_log!(
1456 "lifecycle",
1457 "warn: [LandFix:VscodeWebview] Failed to read {}: {}",
1458 FilesystemPath.display(),
1459 Error
1460 );
1461 },
1462 }
1463 }
1464 }
1465
1466 dev_log!(
1467 "lifecycle",
1468 "warn: [LandFix:VscodeWebview] Not found: {} (resolved: {})",
1469 Uri,
1470 ResolvedPath
1471 );
1472
1473 build_error_response(404, &format!("Not Found: {}", ResolvedPath))
1474}