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}