From 21e52325e92f3b4c9736c7c813f60beaa2de88f1 Mon Sep 17 00:00:00 2001 From: Matthieu Baumann Date: Sun, 31 May 2026 14:58:50 +0200 Subject: [PATCH] fix #376: detect localhost urls and proxyfy them --- src/core/al-core/src/image/fits.rs | 10 ++++++ src/core/src/app.rs | 49 +++++++++++++++++++----------- src/core/src/renderable/mod.rs | 9 +++--- src/js/vo/samp.js | 24 ++++++++++----- 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/core/al-core/src/image/fits.rs b/src/core/al-core/src/image/fits.rs index 4bd7f9ed..4cc2a84e 100644 --- a/src/core/al-core/src/image/fits.rs +++ b/src/core/al-core/src/image/fits.rs @@ -13,6 +13,8 @@ use std::io::Cursor; use std::ops::Range; use wasm_bindgen::JsValue; +use fitsrs::hdu::header::ValueMap; + #[derive(Debug)] pub struct FitsImage<'a> { // Margin values for HiPS3D cubic tiles @@ -37,6 +39,9 @@ pub struct FitsImage<'a> { pub data_byte_offset: Range, // raw bytes of the data image (in Big-Endian) pub raw_bytes: Cow<'a, [u8]>, + + // keep the header keywords and their values + pub header: ValueMap } impl<'a> FitsImage<'a> { @@ -76,6 +81,7 @@ impl<'a> FitsImage<'a> { let wcs = hdu.wcs().ok(); + let values: &ValueMap = &*header; images.push(Self { trim1, trim2, @@ -90,6 +96,7 @@ impl<'a> FitsImage<'a> { blank, data_byte_offset, raw_bytes, + header: values.clone() }); } } @@ -149,6 +156,8 @@ impl<'a> FitsImage<'a> { }; if let Some(raw_bytes) = raw_bytes { + let values: &ValueMap = &*header; + images.push(Self { trim1, trim2, @@ -163,6 +172,7 @@ impl<'a> FitsImage<'a> { blank, data_byte_offset, raw_bytes: Cow::Owned(raw_bytes), + header: values.clone() }); } } diff --git a/src/core/src/app.rs b/src/core/src/app.rs index 0e27f66e..826b8078 100644 --- a/src/core/src/app.rs +++ b/src/core/src/app.rs @@ -1176,7 +1176,7 @@ impl App { layer: String, bytes: &[u8], wcs: WCS, - cfg: ImageMetadata, + options: ImageMetadata, ) -> Result { let gl = self.gl.clone(); @@ -1188,7 +1188,7 @@ impl App { images: vec![image], id: layer.clone(), layer, - meta: cfg, + options, }; let params = layer.get_params(); @@ -1212,9 +1212,11 @@ impl App { pub(crate) fn add_fits_image( &mut self, bytes: &[u8], - meta: ImageMetadata, + options: ImageMetadata, layer: String, ) -> Result { + use fitsrs::hdu::header::ValueMap; + let gl = self.gl.clone(); // Stop the current inertia // And disable it while the fits has not been loaded @@ -1225,19 +1227,20 @@ impl App { let gz = fitsrs::gz::GzReader::new(Cursor::new(bytes)) .map_err(|_| JsValue::from_str("Error creating gz wrapper"))?; - let parse_fits_images_from_bytes = |raw_bytes: &[u8]| -> Result, JsValue> { - Ok(FitsImage::from_raw_bytes(raw_bytes)? + let parse_fits_images_from_bytes = |raw_bytes: &[u8]| -> Result<(Vec, Vec), JsValue> { + let (images, headers) = FitsImage::from_raw_bytes(raw_bytes)? .into_iter() .filter_map( |FitsImage { - bitpix, - bscale, - bzero, - blank, - wcs, - raw_bytes, - .. - }| { + bitpix, + bscale, + bzero, + blank, + wcs, + raw_bytes, + header, + .. + }| { if let Some(wcs) = wcs { let image = Image::from_fits_hdu( &gl, @@ -1250,16 +1253,18 @@ impl App { camera_coo_sys, ) .ok()?; - Some(image) + Some((image, header)) } else { None } }, ) - .collect::>()) + .collect::<(Vec<_>, Vec<_>)>(); + + Ok((images, headers)) }; - let images = match gz { + let (images, headers) = match gz { fitsrs::gz::GzReader::GzReader(bytes) => parse_fits_images_from_bytes(bytes.get_ref())?, fitsrs::gz::GzReader::Reader(bytes) => parse_fits_images_from_bytes(bytes.get_ref())?, }; @@ -1267,12 +1272,13 @@ impl App { if images.is_empty() { Err(JsValue::from_str("no images have been parsed")) } else { + let layer = ImageLayer { images, id: layer.clone(), layer, - meta, + options, }; let params = layer.get_params(); @@ -1284,7 +1290,14 @@ impl App { )?; self.request_redraw = true; - let promise = js_sys::Promise::resolve(&serde_wasm_bindgen::to_value(¶ms)?); + let obj: js_sys::Object = serde_wasm_bindgen::to_value(¶ms)? + .dyn_into()?; + + use std::iter::FromIterator; + let arr = js_sys::Array::from_iter(headers.iter().map(|header| serde_wasm_bindgen::to_value(&header).unwrap())); + js_sys::Reflect::set(&obj, &"headers".into(), &arr).unwrap(); + + let promise = js_sys::Promise::resolve(&obj.into()); Ok(promise) } } diff --git a/src/core/src/renderable/mod.rs b/src/core/src/renderable/mod.rs index 81963ab8..d35dca92 100644 --- a/src/core/src/renderable/mod.rs +++ b/src/core/src/renderable/mod.rs @@ -103,8 +103,8 @@ pub struct ImageLayer { pub layer: String, pub id: String, pub images: Vec, - /// Its color - pub meta: ImageMetadata, + /// Display options + pub options: ImageMetadata, } impl ImageLayer { @@ -464,7 +464,8 @@ impl Layers { layer, id, images, - meta, + options, + .. } = image; // 1. Add the layer name @@ -479,7 +480,7 @@ impl Layers { self.layers.insert(idx, layer.to_string()); // 2. Add the meta information of the layer - self.meta.insert(layer.clone(), meta); + self.meta.insert(layer.clone(), options); // 3. Add the fits image // The layer does not already exist diff --git a/src/js/vo/samp.js b/src/js/vo/samp.js index 1e69c505..6bc04bc5 100644 --- a/src/js/vo/samp.js +++ b/src/js/vo/samp.js @@ -48,6 +48,11 @@ export class SAMPConnector { let callHandler = cc.callHandler; this.cc = cc; + let isLocalhost = (url) => { + const { hostname } = new URL(url); + return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1"; + }; + let self = this; // listen for hub deconnexion/shutdown and unregister if so callHandler["samp.hub.event.shutdown"] = function(senderId, message, isCall) { @@ -91,13 +96,12 @@ export class SAMPConnector { let params = message["samp.params"]; let id = params['table-id']; - let url = params['url']; + let origUrl = params['url']; + let finalUrl = isLocalhost(origUrl) ? cc.connection.translateUrl(origUrl) : origUrl; let name = params['name'] || id; - console.log(id, url, name) - A.catalogFromURL( - url, + finalUrl, {name, onClick: 'showTable'}, // Add the catalog if the query has succeded (catalog) => { @@ -120,11 +124,13 @@ export class SAMPConnector { let params = message["samp.params"]; let id = params['table-id']; - let url = params['url']; + let origUrl = params['url']; + let finalUrl = isLocalhost(origUrl) ? cc.connection.translateUrl(origUrl) : origUrl; + let rowList = params['row-list']; // search for the catalog - let catalog = selectCatalog(id, url) + let catalog = selectCatalog(id, finalUrl) if (catalog) { let objects = []; @@ -140,11 +146,13 @@ export class SAMPConnector { let params = message["samp.params"]; let id = params['table-id']; - let url = params['url']; + let origUrl = params['url']; + let finalUrl = isLocalhost(origUrl) ? cc.connection.translateUrl(origUrl) : origUrl; + let row = params['row']; // search for the catalog - let catalog = selectCatalog(id, url) + let catalog = selectCatalog(id, finalUrl) if (catalog) { const source = catalog.sources[row];