Skip to main content

onelf_format/
update.rs

1//! Update-URL conventions shared by the runtime and the packer.
2//!
3//! The runtime derives the detached-signature URL from the update URL,
4//! and the packer has to predict that name so a publisher uploads the
5//! signature where it will actually be requested. Both sides deriving it
6//! from one function is the point: a signature published under a name the
7//! runtime never asks for fails silently, and only for users who try to
8//! update.
9
10/// Build the detached-signature URL by appending `.sig` to the path
11/// component, before any query string or fragment. `https://h/a?t=1`
12/// becomes `https://h/a.sig?t=1`, preserving query-bearing update URLs.
13///
14/// Note this hangs off the update URL, which points at the zsync control
15/// file, not at the binary. A binary published as `app.onelf` with an
16/// update URL of `app.onelf.zsync` needs its signature at
17/// `app.onelf.zsync.sig`, even though the bytes signed are the binary's.
18pub fn detached_sig_url(url: &str) -> String {
19    let split = url.find(['?', '#']).unwrap_or(url.len());
20    let (path, rest) = url.split_at(split);
21    format!("{path}.sig{rest}")
22}
23
24/// The bare filename a publisher must upload the signature as, derived
25/// from the update URL the package carries.
26pub fn detached_sig_filename(url: &str) -> Option<String> {
27    let sig_url = detached_sig_url(url);
28    let path = sig_url.split(['?', '#']).next()?;
29    let name = path.rsplit('/').next()?;
30    if name.is_empty() {
31        return None;
32    }
33    Some(name.to_string())
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn sig_url_appends_to_the_path_component() {
42        assert_eq!(
43            detached_sig_url("https://h/app.onelf.zsync"),
44            "https://h/app.onelf.zsync.sig"
45        );
46    }
47
48    #[test]
49    fn sig_url_preserves_a_query_string() {
50        assert_eq!(
51            detached_sig_url("https://h/app.onelf.zsync?t=1"),
52            "https://h/app.onelf.zsync.sig?t=1"
53        );
54        assert_eq!(
55            detached_sig_url("https://h/app.onelf.zsync#frag"),
56            "https://h/app.onelf.zsync.sig#frag"
57        );
58    }
59
60    #[test]
61    fn filename_is_what_the_publisher_uploads() {
62        assert_eq!(
63            detached_sig_filename("https://h/d/app.onelf.zsync").as_deref(),
64            Some("app.onelf.zsync.sig")
65        );
66        assert_eq!(
67            detached_sig_filename("https://h/d/app.onelf.zsync?v=2").as_deref(),
68            Some("app.onelf.zsync.sig")
69        );
70    }
71}