soma_self_update/
directive.rs1use std::net::IpAddr;
2
3use url::Url;
4
5use crate::{ArtifactTransportPolicy, Result, UpdateDirective, UpdateError};
6
7impl UpdateDirective {
8 pub fn resolve_artifact_url(
10 &self,
11 endpoint: &Url,
12 policy: ArtifactTransportPolicy,
13 ) -> Result<Url> {
14 if endpoint.cannot_be_a_base() || endpoint.host_str().is_none() {
15 return Err(UpdateError::InvalidBaseUrl {
16 url: endpoint.to_string(),
17 message: "URL must be hierarchical and contain a host".into(),
18 });
19 }
20 let artifact = endpoint.join(self.artifact_url()).map_err(|error| {
21 UpdateError::InvalidArtifactUrl {
22 url: self.artifact_url().to_owned(),
23 message: error.to_string(),
24 }
25 })?;
26 validate_artifact_url(endpoint, &artifact, policy)?;
27 Ok(artifact)
28 }
29
30 pub fn validate_artifact_response_url(
36 &self,
37 endpoint: &Url,
38 response_url: &Url,
39 policy: ArtifactTransportPolicy,
40 ) -> Result<()> {
41 self.resolve_artifact_url(endpoint, policy)?;
42 validate_artifact_url(endpoint, response_url, policy)
43 }
44}
45
46fn validate_artifact_url(
47 endpoint: &Url,
48 artifact: &Url,
49 policy: ArtifactTransportPolicy,
50) -> Result<()> {
51 if artifact.cannot_be_a_base() || artifact.host_str().is_none() {
52 return Err(UpdateError::InvalidArtifactUrl {
53 url: artifact.to_string(),
54 message: "URL must be hierarchical and contain a host".into(),
55 });
56 }
57 let same_origin = endpoint.scheme() == artifact.scheme()
58 && endpoint.host() == artifact.host()
59 && endpoint.port_or_known_default() == artifact.port_or_known_default();
60 if !same_origin {
61 return Err(UpdateError::CrossOriginArtifact {
62 base: endpoint.to_string(),
63 artifact: artifact.to_string(),
64 });
65 }
66 let secure = artifact.scheme() == "https";
67 let loopback_http =
68 artifact.scheme() == "http" && is_loopback(artifact.host_str().unwrap_or_default());
69 if !(secure || policy == ArtifactTransportPolicy::HttpsOrLoopbackHttp && loopback_http) {
70 return Err(UpdateError::InsecureTransport(artifact.to_string()));
71 }
72 Ok(())
73}
74
75fn is_loopback(host: &str) -> bool {
76 host.eq_ignore_ascii_case("localhost")
77 || host
78 .trim_matches(['[', ']'])
79 .parse::<IpAddr>()
80 .is_ok_and(|address| address.is_loopback())
81}