From 099d1d1c550b63f618ef027e2764aba36ce267a0 Mon Sep 17 00:00:00 2001 From: Dionisio Date: Sat, 14 Feb 2026 00:18:59 +0100 Subject: [PATCH] fix: share URL respects scheme in OXICLOUD_SERVER_HOST (#103) When OXICLOUD_SERVER_HOST contains a full URL with scheme (e.g. https://oxi.example.com), the share link no longer prepends http:// or appends :port, avoiding malformed URLs like http://https://host:8085/s/token. - Add AppConfig::base_url() helper with smart URL construction - Replace all 6 inline format!() calls in share_service.rs - Replace inline construction in di.rs (admin settings base URL) - Priority: OXICLOUD_BASE_URL > full-URL host > http://host:port --- src/application/services/share_service.rs | 12 ++++++------ src/common/config.rs | 22 ++++++++++++++++++++++ src/common/di.rs | 4 +--- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/application/services/share_service.rs b/src/application/services/share_service.rs index 35a8554f..3bfd3864 100644 --- a/src/application/services/share_service.rs +++ b/src/application/services/share_service.rs @@ -158,7 +158,7 @@ impl ShareUseCase for ShareService { .map_err(|e| ShareServiceError::Repository(e.to_string()))?; // Convert the entity to DTO for the response - Ok(ShareDto::from_entity(&saved_share, &format!("http://{}:{}", self.config.server_host, self.config.server_port))) + Ok(ShareDto::from_entity(&saved_share, &self.config.base_url())) } async fn get_shared_link(&self, id: &str) -> Result { @@ -175,7 +175,7 @@ impl ShareUseCase for ShareService { } // Convert the entity to DTO for the response - Ok(ShareDto::from_entity(&share, &format!("http://{}:{}", self.config.server_host, self.config.server_port))) + Ok(ShareDto::from_entity(&share, &self.config.base_url())) } async fn get_shared_link_by_token(&self, token: &str) -> Result { @@ -192,7 +192,7 @@ impl ShareUseCase for ShareService { } // Convert the entity to DTO for the response - Ok(ShareDto::from_entity(&share, &format!("http://{}:{}", self.config.server_host, self.config.server_port))) + Ok(ShareDto::from_entity(&share, &self.config.base_url())) } async fn get_shared_links_for_item( @@ -213,7 +213,7 @@ impl ShareUseCase for ShareService { // Convert the entities to DTOs for the response let share_dtos = active_shares .iter() - .map(|s| ShareDto::from_entity(s, &format!("http://{}:{}", self.config.server_host, self.config.server_port))) + .map(|s| ShareDto::from_entity(s, &self.config.base_url())) .collect(); Ok(share_dtos) @@ -264,7 +264,7 @@ impl ShareUseCase for ShareService { .map_err(|e| ShareServiceError::Repository(e.to_string()))?; // Convert the entity to DTO for the response - Ok(ShareDto::from_entity(&updated_share, &format!("http://{}:{}", self.config.server_host, self.config.server_port))) + Ok(ShareDto::from_entity(&updated_share, &self.config.base_url())) } async fn delete_shared_link(&self, id: &str) -> Result<(), DomainError> { @@ -296,7 +296,7 @@ impl ShareUseCase for ShareService { // Convert the entities to DTOs let share_dtos: Vec = shares .iter() - .map(|s| ShareDto::from_entity(s, &format!("http://{}:{}", self.config.server_host, self.config.server_port))) + .map(|s| ShareDto::from_entity(s, &self.config.base_url())) .collect(); // Create the paginated result diff --git a/src/common/config.rs b/src/common/config.rs index 353c9b44..3e416a04 100644 --- a/src/common/config.rs +++ b/src/common/config.rs @@ -580,6 +580,28 @@ impl AppConfig { pub fn auth_enabled(&self) -> bool { self.features.enable_auth } + + /// Build the public base URL for generating share links and other external URLs. + /// + /// Priority: + /// 1. `OXICLOUD_BASE_URL` env var (used as-is) + /// 2. If `server_host` already contains a scheme (`http://` or `https://`), + /// treat it as a full origin and do **not** prepend a scheme or append a port. + /// 3. Otherwise, fall back to `http://{server_host}:{server_port}`. + pub fn base_url(&self) -> String { + if let Ok(explicit) = std::env::var("OXICLOUD_BASE_URL") { + return explicit.trim_end_matches('/').to_string(); + } + + let host = self.server_host.trim_end_matches('/'); + + if host.starts_with("http://") || host.starts_with("https://") { + // The user already provided a full origin — use it directly. + host.to_string() + } else { + format!("http://{}:{}", host, self.server_port) + } + } } /// Gets a default global configuration diff --git a/src/common/di.rs b/src/common/di.rs index a72f56c9..07bca866 100644 --- a/src/common/di.rs +++ b/src/common/di.rs @@ -594,9 +594,7 @@ impl AppServiceFactory { let settings_repo = Arc::new( crate::infrastructure::repositories::pg::SettingsPgRepository::new(pool.clone()) ); - let server_base_url = std::env::var("OXICLOUD_BASE_URL").unwrap_or_else(|_| { - format!("http://{}:{}", self.config.server_host, self.config.server_port) - }); + let server_base_url = self.config.base_url(); // Load OIDC config from env vars (the snapshot from startup) let env_oidc = crate::common::config::OidcConfig::from_env();