Pin WebUI to 1.x

This commit is contained in:
Maurus Decimus committed 2026-10-18 19:54:34 +02:00
1 parent 2321f1b5aa
commit aa47d8ef7b
3 files changed
+77 -7

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
## [0.16.27] - 2026-10-XX
## [0.16.27] - 2026-10-18
If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If you are upgrading from v0.15.x and below, please read the [upgrading documentation](https://github.com/stalwartlabs/stalwart/blob/main/UPGRADING/v0_16.md) for more information on how to upgrade from previous versions.
+69 -3
View File
@@ -4,13 +4,19 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{Server, manager::fetch_resource};
use crate::{
Server,
manager::{
defaults::{WEBUI_LATEST_URL, WEBUI_MAX_MAJOR_VERSION, WEBUI_PINNED_URL},
fetch_resource,
},
};
use ahash::AHashMap;
use arc_swap::ArcSwap;
use registry::schema::{enums::CompressionAlgo, structs::Application};
use std::{
borrow::Cow,
io::{self, Cursor, Read},
io::{self, Cursor, Read, Seek},
path::{Path, PathBuf},
sync::{
Arc,
@@ -197,7 +203,11 @@ impl WebApplicationManager {
Self {
base_path,
blob_key: BlobHash::generate(format!("{}{}", APP_BLOB_PREFIX, app.id.id()).as_bytes()),
url: app.object.resource_url,
url: if app.object.resource_url == WEBUI_LATEST_URL {
WEBUI_PINNED_URL.to_string()
} else {
app.object.resource_url
},
description: app.object.description,
expiry: app.object.auto_update_frequency.as_secs(),
oauth_client_id: app.object.oauth_client_id,
@@ -261,6 +271,15 @@ impl WebApplicationManager {
.ctx(Key::Url, url.clone())
.details("Failed to decompress application bundle")
})?;
if let Some(version) = unsupported_bundle_version(&mut archive) {
return Err(trc::ResourceEvent::Error
.caused_by(trc::location!())
.ctx(Key::Url, url.clone())
.details(format!(
"Application bundle version {version} requires Stalwart v1 or later, set the resource URL to {WEBUI_PINNED_URL}"
)));
}
let mut resources = AHashMap::with_capacity(archive.len());
for i in 0..archive.len() {
let mut file = archive.by_index(i).map_err(|err| {
@@ -496,6 +515,19 @@ fn rewrite_index(html: &str, prefix: &str, oauth_client_id_meta: Option<&str>) -
out.into_bytes()
}
fn unsupported_bundle_version<R: Read + Seek>(archive: &mut zip::ZipArchive<R>) -> Option<String> {
let file = archive.by_name("VERSION").ok()?;
let mut version = String::new();
let is_supported = file.take(32).read_to_string(&mut version).is_ok()
&& version
.trim()
.split('.')
.next()
.and_then(|major| major.parse::<u32>().ok())
.is_some_and(|major| major <= WEBUI_MAX_MAJOR_VERSION);
(!is_supported).then(|| version.trim().to_string())
}
fn oauth_client_id_meta(client_id: &str) -> String {
let mut meta = String::with_capacity(OAUTH_CLIENT_ID.len() + client_id.len());
meta.push_str("<meta name=\"oauth-client-id\" content=\"");
@@ -750,4 +782,38 @@ mod tests {
assert_ne!(apps.next_generation(), apps.next_generation());
}
fn bundle(version: Option<&str>) -> zip::ZipArchive<Cursor<Vec<u8>>> {
let mut writer = zip::ZipWriter::new(Cursor::new(Vec::new()));
let options = zip::write::SimpleFileOptions::default();
writer.start_file("index.html", options).unwrap();
io::Write::write_all(&mut writer, INDEX.as_bytes()).unwrap();
if let Some(version) = version {
writer.start_file("VERSION", options).unwrap();
io::Write::write_all(&mut writer, version.as_bytes()).unwrap();
}
zip::ZipArchive::new(writer.finish().unwrap()).unwrap()
}
#[test]
fn bundles_newer_than_the_supported_major_version_are_rejected() {
// Bundles without VERSION or with a 1.x VERSION are accepted, anything else is rejected
assert_eq!(unsupported_bundle_version(&mut bundle(None)), None);
assert_eq!(
unsupported_bundle_version(&mut bundle(Some("1.0.12\n"))),
None
);
assert_eq!(
unsupported_bundle_version(&mut bundle(Some("2.0.0\n"))),
Some("2.0.0".to_string())
);
assert_eq!(
unsupported_bundle_version(&mut bundle(Some("10.1.0"))),
Some("10.1.0".to_string())
);
assert_eq!(
unsupported_bundle_version(&mut bundle(Some("garbage"))),
Some("garbage".to_string())
);
}
}
+7 -3
View File
@@ -26,6 +26,12 @@ use store::{
},
};
pub const WEBUI_LATEST_URL: &str =
"https://github.com/stalwartlabs/webui/releases/latest/download/webui.zip";
pub const WEBUI_PINNED_URL: &str =
"https://github.com/stalwartlabs/webui/releases/download/v1.0.11/webui.zip";
pub const WEBUI_MAX_MAJOR_VERSION: u32 = 1;
pub const ASN_IPV4: &str =
"https://github.com/sapics/ip-location-db/releases/download/latest/origin-asn-ipv4.csv";
pub const ASN_IPV6: &str =
@@ -63,9 +69,7 @@ async fn insert_safe_defaults(bp: &mut Bootstrap) -> trc::Result<()> {
description: "Stalwart Web Interface".to_string(),
enabled: true,
#[cfg(not(feature = "dev_mode"))]
resource_url:
"https://github.com/stalwartlabs/webui/releases/latest/download/webui.zip"
.into(),
resource_url: WEBUI_PINNED_URL.into(),
#[cfg(feature = "dev_mode")]
resource_url: "file:///Users/me/code/webui/.ignore/webui.zip".into(),
unpack_directory: None,