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 parse_http_response(&buffer)
348 })
349 })
350 .join()
351 .map_err(|e| format!("Thread panicked: {:?}", e))?;
352
353 result
354}
355
356fn parse_http_response(response:&[u8]) -> Result<(u16, Vec<u8>, HashMap<String, String>), String> {
361 let headers_end = response
362 .windows(4)
363 .position(|w| w == b"\r\n\r\n")
364 .ok_or("Invalid HTTP response: no headers/body separator")?;
365
366 let headers_str =
367 std::str::from_utf8(&response[..headers_end]).map_err(|e| format!("Invalid UTF-8 in HTTP headers: {}", e))?;
368
369 let body = response[headers_end + 4..].to_vec();
370
371 let mut lines = headers_str.lines();
373
374 let status_line = lines.next().ok_or("Invalid HTTP response: no status line")?;
375
376 let status = status_line
378 .split_whitespace()
379 .nth(1)
380 .and_then(|s| s.parse::<u16>().ok())
381 .ok_or_else(|| format!("Invalid status line: {}", status_line))?;
382
383 let mut headers = HashMap::new();
385
386 for line in lines {
387 if let Some((name, value)) = line.split_once(':') {
388 headers.insert(name.trim().to_lowercase(), value.trim().to_string());
389 }
390 }
391
392 Ok((status, body, headers))
393}
394
395pub fn land_scheme_handler(request:&Request<Vec<u8>>) -> Response<Vec<u8>> {
435 init_cache();
437
438 let uri = request.uri().to_string();
440
441 dev_log!("lifecycle", "[Scheme] Handling land:// request: {}", uri);
442
443 let (domain, path) = match parse_land_uri(&uri) {
445 Ok(result) => result,
446
447 Err(e) => {
448 dev_log!("lifecycle", "error: [Scheme] Failed to parse URI: {}", e);
449
450 return build_error_response(400, &format!("Bad Request: {}", e));
451 },
452 };
453
454 if request.method() == Method::OPTIONS {
456 dev_log!("lifecycle", "[Scheme] Handling CORS preflight request");
457
458 return build_cors_preflight_response();
459 }
460
461 if should_cache(&path) {
463 if let Some(cached) = get_cached(&path) {
464 dev_log!("lifecycle", "[Scheme] Cache hit for: {}", path);
465
466 return build_cached_response(cached);
467 }
468 }
469
470 let registry = match get_service_registry() {
472 Some(r) => r,
473
474 None => {
475 dev_log!("lifecycle", "error: [Scheme] Service registry not initialized");
476
477 return build_error_response(503, "Service Unavailable: Registry not initialized");
478 },
479 };
480
481 let service = match registry.lookup(&domain) {
482 Some(s) => s,
483
484 None => {
485 dev_log!("lifecycle", "warn: [Scheme] Service not found: {}", domain);
486
487 return build_error_response(404, &format!("Not Found: Service {} not registered", domain));
488 },
489 };
490
491 let local_url = format!("http://127.0.0.1:{}{}", service.port, path);
493
494 dev_log!(
495 "lifecycle",
496 "[Scheme] Routing {} {} to local service at {}",
497 request.method(),
498 uri,
499 local_url
500 );
501
502 let result = forward_http_request(&local_url, request, request.method().clone());
504
505 match result {
506 Ok((status, body, headers)) => {
507 let body_bytes = body.clone();
509
510 let LowerPath = path.to_ascii_lowercase();
521
522 let IsAssetRequest = LowerPath.ends_with(".js")
523 || LowerPath.ends_with(".mjs")
524 || LowerPath.ends_with(".cjs")
525 || LowerPath.ends_with(".json")
526 || LowerPath.ends_with(".map")
527 || LowerPath.ends_with(".css")
528 || LowerPath.ends_with(".wasm")
529 || LowerPath.ends_with(".svg")
530 || LowerPath.ends_with(".png")
531 || LowerPath.ends_with(".woff")
532 || LowerPath.ends_with(".woff2")
533 || LowerPath.ends_with(".ttf")
534 || LowerPath.ends_with(".otf");
535
536 let UpstreamSaysHtml = headers
537 .get("content-type")
538 .map(|V| V.to_ascii_lowercase().contains("text/html"))
539 .unwrap_or(false);
540
541 if IsAssetRequest && (status == 404 || (status >= 400 && UpstreamSaysHtml)) {
542 dev_log!(
543 "scheme-assets",
544 "[LandFix:Mime] swap HTML 404 → text/plain empty for asset path={} status={}",
545 path,
546 status
547 );
548
549 return Builder::new()
550 .status(404)
551 .header("Content-Type", "text/plain; charset=utf-8")
552 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
553 .body(Vec::<u8>::new())
554 .unwrap_or_else(|_| build_error_response(500, "Failed to build 404 response"));
555 }
556
557 let mut response_builder = Builder::new()
559 .status(status)
560 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
561 .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
562 .header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
563
564 let important_headers = [
566 "content-type",
567 "content-length",
568 "etag",
569 "last-modified",
570 "cache-control",
571 "expires",
572 "content-encoding",
573 "content-disposition",
574 "location",
575 ];
576
577 for header_name in &important_headers {
578 if let Some(value) = headers.get(*header_name) {
579 response_builder = response_builder.header(*header_name, value);
580 }
581 }
582
583 let response = response_builder.body(body_bytes);
584
585 if status == 200 && should_cache(&path) {
587 let content_type = headers
588 .get("content-type")
589 .unwrap_or(&"application/octet-stream".to_string())
590 .clone();
591
592 let cache_control = headers
593 .get("cache-control")
594 .unwrap_or(&"public, max-age=3600".to_string())
595 .clone();
596
597 let etag = headers.get("etag").cloned();
598
599 let last_modified = headers.get("last-modified").cloned();
600
601 let entry = CacheEntry { body, content_type, cache_control, etag, last_modified };
602
603 set_cached(&path, entry);
604
605 dev_log!("lifecycle", "[Scheme] Cached response for: {}", path);
606 }
607
608 response.unwrap_or_else(|_| build_error_response(500, "Internal Server Error"))
609 },
610
611 Err(e) => {
612 dev_log!("lifecycle", "error: [Scheme] Failed to forward request: {}", e);
613
614 build_error_response(503, &format!("Service Unavailable: {}", e))
615 },
616 }
617}
618
619fn build_error_response(status:u16, message:&str) -> Response<Vec<u8>> {
621 let body = serde_json::json!({
622 "error": message,
623 "status": status
624 });
625
626 Builder::new()
627 .status(status)
628 .header("Content-Type", "application/json")
629 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
630 .body(serde_json::to_vec(&body).unwrap_or_default())
631 .unwrap_or_else(|_| Builder::new().status(500).body(Vec::new()).unwrap())
632}
633
634fn build_cors_preflight_response() -> Response<Vec<u8>> {
636 Builder::new()
637 .status(204)
638 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
639 .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
640 .header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
641 .header("Access-Control-Max-Age", "86400")
642 .body(Vec::new())
643 .unwrap()
644}
645
646fn build_cached_response(entry:CacheEntry) -> Response<Vec<u8>> {
648 let mut builder = Builder::new()
649 .status(200)
650 .header("Content-Type", &entry.content_type)
651 .header("Access-Control-Allow-Origin", "land://code.land.playform.cloud")
652 .header("Cache-Control", &entry.cache_control);
653
654 if let Some(etag) = &entry.etag {
655 builder = builder.header("ETag", etag);
656 }
657
658 if let Some(last_modified) = &entry.last_modified {
659 builder = builder.header("Last-Modified", last_modified);
660 }
661
662 builder
663 .body(entry.body)
664 .unwrap_or_else(|_| build_error_response(500, "Internal Server Error"))
665}
666
667pub fn register_land_service(name:&str, port:u16) {
676 let registry = get_service_registry().expect("Service registry not initialized. Call init_service_registry first.");
677
678 registry.register(name.to_string(), port, Some("/health".to_string()));
679
680 dev_log!("lifecycle", "[Scheme] Registered service: {} -> {}", name, port);
681}
682
683pub fn get_land_port(name:&str) -> Option<u16> {
694 let registry = get_service_registry()?;
695
696 registry.lookup(name).map(|s| s.port)
697}
698
699pub fn land_scheme_handler_async<R:tauri::Runtime>(
732 _ctx:tauri::UriSchemeContext<'_, R>,
733
734 request:tauri::http::request::Request<Vec<u8>>,
735
736 responder:tauri::UriSchemeResponder,
737) {
738 std::thread::spawn(move || {
740 let response = land_scheme_handler(&request);
741 responder.respond(response);
742 });
743}
744
745fn get_cors_origins() -> &'static str {
754 "land://localhost, http://land.localhost, land://code.land.playform.cloud"
756}
757
758#[inline]
763pub fn Scheme() {}
764
765fn MimeFromExtension(Path:&str) -> &'static str {
771 if Path.ends_with(".js") || Path.ends_with(".mjs") {
772 "application/javascript"
773 } else if Path.ends_with(".css") {
774 "text/css"
775 } else if Path.ends_with(".html") || Path.ends_with(".htm") {
776 "text/html"
777 } else if Path.ends_with(".json") {
778 "application/json"
779 } else if Path.ends_with(".svg") {
780 "image/svg+xml"
781 } else if Path.ends_with(".png") {
782 "image/png"
783 } else if Path.ends_with(".jpg") || Path.ends_with(".jpeg") {
784 "image/jpeg"
785 } else if Path.ends_with(".gif") {
786 "image/gif"
787 } else if Path.ends_with(".woff") {
788 "font/woff"
789 } else if Path.ends_with(".woff2") {
790 "font/woff2"
791 } else if Path.ends_with(".ttf") {
792 "font/ttf"
793 } else if Path.ends_with(".wasm") {
794 "application/wasm"
795 } else if Path.ends_with(".map") {
796 "application/json"
797 } else if Path.ends_with(".txt") || Path.ends_with(".md") {
798 "text/plain"
799 } else if Path.ends_with(".xml") {
800 "application/xml"
801 } else {
802 "application/octet-stream"
803 }
804}
805
806pub fn VscodeFileSchemeHandler<R:tauri::Runtime>(
835 AppHandle:&tauri::AppHandle<R>,
836
837 Request:&tauri::http::request::Request<Vec<u8>>,
838) -> Response<Vec<u8>> {
839 let Result = catch_unwind(AssertUnwindSafe(|| _VscodeFileSchemeHandler(AppHandle, Request)));
845
846 match Result {
847 Ok(Response) => Response,
848
849 Err(Panic) => {
850 let Info = if let Some(Text) = Panic.downcast_ref::<&str>() {
851 Text.to_string()
852 } else if let Some(Text) = Panic.downcast_ref::<String>() {
853 Text.clone()
854 } else {
855 "unknown panic".to_string()
856 };
857
858 dev_log!(
859 "lifecycle",
860 "error: [LandFix:VscodeFile] caught panic in scheme handler: {}",
861 Info
862 );
863
864 build_error_response(500, &format!("Internal Server Error (caught panic: {})", Info))
865 },
866 }
867}
868
869fn _VscodeFileSchemeHandler<R:tauri::Runtime>(
870 AppHandle:&tauri::AppHandle<R>,
871
872 Request:&tauri::http::request::Request<Vec<u8>>,
873) -> Response<Vec<u8>> {
874 let Uri = Request.uri().to_string();
875
876 dev_log!("scheme-assets", "[LandFix:VscodeFile] Request: {}", Uri);
882
883 dev_log!("scheme-assets", "[SchemeAssets] request uri={}", Uri);
884
885 let FilePath = Uri
900 .strip_prefix("vscode-file://vscode-app/")
901 .or_else(|| Uri.strip_prefix("vscode-file://vscode-app"))
902 .or_else(|| {
903 let After = Uri.strip_prefix("vscode-file://")?;
906 let SlashIdx = After.find('/')?;
907 Some(&After[SlashIdx + 1..])
908 })
909 .unwrap_or("");
910
911 let CleanPath = if FilePath.starts_with("Static/Application//out/") {
914 FilePath.replacen("Static/Application//out/", "Static/Application/", 1)
915 } else if FilePath.starts_with("Static/Application/out/") {
916 FilePath.replacen("Static/Application/out/", "Static/Application/", 1)
917 } else {
918 FilePath.to_string()
919 };
920
921 let CleanPath = if CleanPath.starts_with("Static/node_modules/") {
925 CleanPath.replacen("Static/node_modules/", "Static/Application/node_modules/", 1)
926 } else {
927 CleanPath
928 };
929
930 let CleanPath = match CleanPath.split_once(['?', '#']) {
943 Some((Before, _)) => Before.to_string(),
944
945 None => CleanPath,
946 };
947
948 if CleanPath.ends_with(".map") {
956 return Builder::new()
957 .status(204)
958 .header("Access-Control-Allow-Origin", "*")
959 .header("Cross-Origin-Resource-Policy", "cross-origin")
960 .body(Vec::new())
961 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
962 }
963
964 if CleanPath.ends_with(".css") && CleanPath.starts_with("Static/Application/") {
992 let LocalPath = format!("/Static/Application/{}", CleanPath.trim_start_matches("Static/Application/"));
993
994 let Body = format!("globalThis._LOAD_CSS_WORKER?.({:?}); export default {{}};", LocalPath);
995
996 dev_log!(
997 "scheme-assets",
998 "[LandFix:VscodeFile] css-shim {} -> _LOAD_CSS_WORKER({})",
999 CleanPath,
1000 LocalPath
1001 );
1002
1003 return Builder::new()
1004 .status(200)
1005 .header("Content-Type", "application/javascript; charset=utf-8")
1006 .header("Access-Control-Allow-Origin", "*")
1007 .header("Cross-Origin-Resource-Policy", "cross-origin")
1008 .header("Cross-Origin-Embedder-Policy", "require-corp")
1009 .header("Cache-Control", "public, max-age=31536000, immutable")
1010 .body(Body.into_bytes())
1011 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1012 }
1013
1014 let IsAbsoluteOSPath = [
1022 "Volumes/",
1023 "Users/",
1024 "Library/",
1025 "System/",
1026 "Applications/",
1027 "private/",
1028 "tmp/",
1029 "var/",
1030 "etc/",
1031 "opt/",
1032 "home/",
1033 "usr/",
1034 "srv/",
1035 "mnt/",
1036 "root/",
1037 ]
1038 .iter()
1039 .any(|Prefix| CleanPath.starts_with(Prefix));
1040
1041 if IsAbsoluteOSPath {
1042 let AbsolutePath = format!("/{}", CleanPath);
1043
1044 let FilesystemPath = std::path::Path::new(&AbsolutePath);
1045
1046 dev_log!(
1047 "scheme-assets",
1048 "[LandFix:VscodeFile] os-abs candidate {} (exists={}, is_file={})",
1049 AbsolutePath,
1050 FilesystemPath.exists(),
1051 FilesystemPath.is_file()
1052 );
1053
1054 if FilesystemPath.exists() && FilesystemPath.is_file() {
1055 match crate::Cache::AssetMemoryMap::LoadOrInsert::Fn(FilesystemPath) {
1061 Ok(Entry) => {
1062 let AcceptsBrotli = Request
1063 .headers()
1064 .get("accept-encoding")
1065 .and_then(|V| V.to_str().ok())
1066 .map(|S| S.contains("br"))
1067 .unwrap_or(false);
1068
1069 let (Body, Encoding):(Vec<u8>, Option<&str>) = if AcceptsBrotli {
1070 match Entry.AsBrotliSlice() {
1071 Some(Slice) => (Slice.to_vec(), Some("br")),
1072
1073 None => (Entry.AsSlice().to_vec(), None),
1074 }
1075 } else {
1076 (Entry.AsSlice().to_vec(), None)
1077 };
1078
1079 dev_log!(
1080 "scheme-assets",
1081 "[LandFix:VscodeFile] os-abs served {} ({}, {} bytes, encoding={:?})",
1082 AbsolutePath,
1083 Entry.Mime,
1084 Body.len(),
1085 Encoding
1086 );
1087
1088 let mut B = Builder::new()
1098 .status(200)
1099 .header("Content-Type", Entry.Mime)
1100 .header("Access-Control-Allow-Origin", "*")
1101 .header("Cross-Origin-Resource-Policy", "cross-origin")
1102 .header("Cross-Origin-Embedder-Policy", "require-corp")
1103 .header("Cache-Control", "public, max-age=3600");
1104
1105 if let Some(Enc) = Encoding {
1106 B = B.header("Content-Encoding", Enc);
1107 }
1108
1109 return B
1110 .body(Body)
1111 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1112 },
1113
1114 Err(Error) => {
1115 dev_log!(
1116 "lifecycle",
1117 "warn: [LandFix:VscodeFile] os-abs mmap failure {}: {}",
1118 AbsolutePath,
1119 Error
1120 );
1121 },
1122 }
1123 } else {
1124 dev_log!("lifecycle", "warn: [LandFix:VscodeFile] os-abs not on disk: {}", AbsolutePath);
1125 }
1126 }
1127
1128 dev_log!("lifecycle", "[LandFix:VscodeFile] Resolved path: {}", CleanPath);
1129
1130 let AssetResult = AppHandle.asset_resolver().get(CleanPath.clone());
1134
1135 if let Some(Asset) = AssetResult {
1136 let Mime = MimeFromExtension(&CleanPath);
1137
1138 dev_log!(
1139 "lifecycle",
1140 "[LandFix:VscodeFile] Serving (embedded) {} ({}, {} bytes)",
1141 CleanPath,
1142 Mime,
1143 Asset.bytes.len()
1144 );
1145
1146 dev_log!(
1147 "scheme-assets",
1148 "[SchemeAssets] serve source=embedded path={} mime={} bytes={}",
1149 CleanPath,
1150 Mime,
1151 Asset.bytes.len()
1152 );
1153
1154 return Builder::new()
1155 .status(200)
1156 .header("Content-Type", Mime)
1157 .header("Access-Control-Allow-Origin", "*")
1158 .header("Cross-Origin-Resource-Policy", "cross-origin")
1159 .header("Cross-Origin-Embedder-Policy", "require-corp")
1160 .header("Cache-Control", "public, max-age=31536000, immutable")
1161 .body(Asset.bytes.to_vec())
1162 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1163 }
1164
1165 let StaticRoot = crate::IPC::WindServiceHandlers::Utilities::ApplicationRoot::Get::Fn();
1167
1168 if let Some(Root) = StaticRoot {
1169 let FilesystemPath = std::path::Path::new(&Root).join(&CleanPath);
1170
1171 if FilesystemPath.exists() && FilesystemPath.is_file() {
1172 match crate::Cache::AssetMemoryMap::LoadOrInsert::Fn(&FilesystemPath) {
1176 Ok(Entry) => {
1177 let AcceptsBrotli = Request
1178 .headers()
1179 .get("accept-encoding")
1180 .and_then(|V| V.to_str().ok())
1181 .map(|S| S.contains("br"))
1182 .unwrap_or(false);
1183
1184 let (Body, Encoding):(Vec<u8>, Option<&str>) = if AcceptsBrotli {
1185 match Entry.AsBrotliSlice() {
1186 Some(Slice) => (Slice.to_vec(), Some("br")),
1187
1188 None => (Entry.AsSlice().to_vec(), None),
1189 }
1190 } else {
1191 (Entry.AsSlice().to_vec(), None)
1192 };
1193
1194 dev_log!(
1195 "lifecycle",
1196 "[LandFix:VscodeFile] Serving (fs-mmap) {} ({}, {} bytes, encoding={:?})",
1197 CleanPath,
1198 Entry.Mime,
1199 Body.len(),
1200 Encoding
1201 );
1202
1203 let mut B = Builder::new()
1213 .status(200)
1214 .header("Content-Type", Entry.Mime)
1215 .header("Access-Control-Allow-Origin", "*")
1216 .header("Cross-Origin-Resource-Policy", "cross-origin")
1217 .header("Cross-Origin-Embedder-Policy", "require-corp")
1218 .header("Cache-Control", "public, max-age=3600");
1219
1220 if let Some(Enc) = Encoding {
1221 B = B.header("Content-Encoding", Enc);
1222 }
1223
1224 return B
1225 .body(Body)
1226 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1227 },
1228
1229 Err(Error) => {
1230 dev_log!(
1231 "lifecycle",
1232 "warn: [LandFix:VscodeFile] Failed to read {}: {}",
1233 FilesystemPath.display(),
1234 Error
1235 );
1236 },
1237 }
1238 }
1239 }
1240
1241 dev_log!(
1242 "lifecycle",
1243 "warn: [LandFix:VscodeFile] Not found: {} (resolved: {})",
1244 Uri,
1245 CleanPath
1246 );
1247
1248 build_error_response(404, &format!("Not Found: {}", CleanPath))
1249}
1250
1251pub fn VscodeWebviewSchemeHandler<R:tauri::Runtime>(
1304 AppHandle:&tauri::AppHandle<R>,
1305
1306 Request:&tauri::http::request::Request<Vec<u8>>,
1307) -> Response<Vec<u8>> {
1308 let Result = catch_unwind(AssertUnwindSafe(|| _VscodeWebviewSchemeHandler(AppHandle, Request)));
1309
1310 match Result {
1311 Ok(Response) => Response,
1312
1313 Err(Panic) => {
1314 let Info = if let Some(Text) = Panic.downcast_ref::<&str>() {
1315 Text.to_string()
1316 } else if let Some(Text) = Panic.downcast_ref::<String>() {
1317 Text.clone()
1318 } else {
1319 "unknown panic".to_string()
1320 };
1321
1322 dev_log!(
1323 "lifecycle",
1324 "error: [LandFix:VscodeWebview] caught panic in scheme handler: {}",
1325 Info
1326 );
1327
1328 build_error_response(500, &format!("Internal Server Error (caught panic: {})", Info))
1329 },
1330 }
1331}
1332
1333fn _VscodeWebviewSchemeHandler<R:tauri::Runtime>(
1334 AppHandle:&tauri::AppHandle<R>,
1335
1336 Request:&tauri::http::request::Request<Vec<u8>>,
1337) -> Response<Vec<u8>> {
1338 let Uri = Request.uri().to_string();
1339
1340 dev_log!("scheme-assets", "[LandFix:VscodeWebview] Request: {}", Uri);
1341
1342 let After = match Uri.strip_prefix("vscode-webview://") {
1347 Some(Rest) => Rest,
1348
1349 None => {
1350 return build_error_response(400, "vscode-webview scheme without prefix");
1351 },
1352 };
1353
1354 let PathStart = match After.find('/') {
1355 Some(Index) => Index + 1,
1356
1357 None => {
1358 return build_error_response(400, "vscode-webview URI missing path component");
1359 },
1360 };
1361
1362 let PathPlusQuery = &After[PathStart..];
1363
1364 let CleanPath:&str = PathPlusQuery
1366 .split_once(|C:char| C == '?' || C == '#')
1367 .map(|(Path, _)| Path)
1368 .unwrap_or(PathPlusQuery);
1369
1370 if CleanPath.is_empty() || CleanPath.contains("..") {
1374 return build_error_response(404, "vscode-webview path empty or traversal");
1375 }
1376
1377 let ResolvedPath = format!("Static/Application/vs/workbench/contrib/webview/browser/pre/{}", CleanPath);
1378
1379 dev_log!(
1380 "scheme-assets",
1381 "[LandFix:VscodeWebview] resolve {} -> {}",
1382 CleanPath,
1383 ResolvedPath
1384 );
1385
1386 if let Some(Asset) = AppHandle.asset_resolver().get(ResolvedPath.clone()) {
1391 let Mime = MimeFromExtension(&ResolvedPath);
1392
1393 dev_log!(
1394 "scheme-assets",
1395 "[LandFix:VscodeWebview] serve embedded {} ({}, {} bytes)",
1396 ResolvedPath,
1397 Mime,
1398 Asset.bytes.len()
1399 );
1400
1401 return Builder::new()
1402 .status(200)
1403 .header("Content-Type", Mime)
1404 .header("Access-Control-Allow-Origin", "*")
1405 .header("Cross-Origin-Embedder-Policy", "require-corp")
1406 .header("Cross-Origin-Resource-Policy", "cross-origin")
1407 .header("Cache-Control", "no-cache")
1408 .body(Asset.bytes.to_vec())
1409 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1410 }
1411
1412 let StaticRoot = crate::IPC::WindServiceHandlers::Utilities::ApplicationRoot::Get::Fn();
1417
1418 if let Some(Root) = StaticRoot {
1419 let FilesystemPath = std::path::Path::new(&Root).join(&ResolvedPath);
1420
1421 if FilesystemPath.exists() && FilesystemPath.is_file() {
1422 match std::fs::read(&FilesystemPath) {
1423 Ok(Bytes) => {
1424 let Mime = MimeFromExtension(&ResolvedPath);
1425
1426 dev_log!(
1427 "scheme-assets",
1428 "[LandFix:VscodeWebview] serve filesystem {} ({}, {} bytes)",
1429 FilesystemPath.display(),
1430 Mime,
1431 Bytes.len()
1432 );
1433
1434 return Builder::new()
1435 .status(200)
1436 .header("Content-Type", Mime)
1437 .header("Access-Control-Allow-Origin", "*")
1438 .header("Cross-Origin-Embedder-Policy", "require-corp")
1439 .header("Cross-Origin-Resource-Policy", "cross-origin")
1440 .header("Cache-Control", "no-cache")
1441 .body(Bytes)
1442 .unwrap_or_else(|_| build_error_response(500, "Failed to build response"));
1443 },
1444
1445 Err(Error) => {
1446 dev_log!(
1447 "lifecycle",
1448 "warn: [LandFix:VscodeWebview] Failed to read {}: {}",
1449 FilesystemPath.display(),
1450 Error
1451 );
1452 },
1453 }
1454 }
1455 }
1456
1457 dev_log!(
1458 "lifecycle",
1459 "warn: [LandFix:VscodeWebview] Not found: {} (resolved: {})",
1460 Uri,
1461 ResolvedPath
1462 );
1463
1464 build_error_response(404, &format!("Not Found: {}", ResolvedPath))
1465}