pacenotes_installer\http/
url_target.rs1pub struct UrlTarget {
3 pub host: String,
4 pub port: u16,
5 pub path: String,
7 pub secure: bool,
8}
9
10impl UrlTarget {
11 pub fn parse(url: &str) -> Result<UrlTarget, super::HttpError> {
13 let wide = crate::i18n::to_wide(url);
14 let mut components: windows_sys::Win32::Networking::WinHttp::URL_COMPONENTS =
15 unsafe { core::mem::zeroed() };
16 components.dwStructSize =
17 core::mem::size_of::<windows_sys::Win32::Networking::WinHttp::URL_COMPONENTS>() as u32;
18 components.dwSchemeLength = u32::MAX;
20 components.dwHostNameLength = u32::MAX;
21 components.dwUrlPathLength = u32::MAX;
22 components.dwExtraInfoLength = u32::MAX;
23
24 let length_without_nul = (wide.len() - 1) as u32;
25 let cracked = unsafe {
26 windows_sys::Win32::Networking::WinHttp::WinHttpCrackUrl(
27 wide.as_ptr(),
28 length_without_nul,
29 0,
30 &mut components,
31 )
32 };
33 if cracked == 0 {
34 return Err(super::HttpError::last(&format!("cannot parse URL {url}")));
35 }
36
37 let mut path = wide_slice(components.lpszUrlPath, components.dwUrlPathLength);
38 path.push_str(&wide_slice(
39 components.lpszExtraInfo,
40 components.dwExtraInfoLength,
41 ));
42 if path.is_empty() {
43 path.push('/');
44 }
45 Ok(UrlTarget {
46 host: wide_slice(components.lpszHostName, components.dwHostNameLength),
47 port: components.nPort,
48 path,
49 secure: components.nScheme
50 == windows_sys::Win32::Networking::WinHttp::WINHTTP_INTERNET_SCHEME_HTTPS,
51 })
52 }
53
54 pub fn origin(&self) -> (bool, String, u16) {
57 (self.secure, self.host.to_ascii_lowercase(), self.port)
58 }
59
60 pub fn url(&self) -> String {
62 format!(
63 "{}://{}:{}{}",
64 self.scheme(),
65 self.host,
66 self.port,
67 self.path
68 )
69 }
70
71 pub fn resolve(&self, location: &str) -> Result<UrlTarget, super::HttpError> {
74 let location = location.trim();
75 if location.is_empty() {
76 return Err(super::HttpError::logical("empty Location header", None));
77 }
78 let lower = location.to_ascii_lowercase();
79 if lower.starts_with("http://") || lower.starts_with("https://") {
80 return UrlTarget::parse(location);
81 }
82 let base = match location.starts_with('/') {
83 true => "",
84 false => self.path.rfind('/').map_or("/", |at| &self.path[..=at]),
85 };
86 let (scheme, host, port) = (self.scheme(), &self.host, self.port);
87 UrlTarget::parse(&format!("{scheme}://{host}:{port}{base}{location}"))
88 }
89
90 fn scheme(&self) -> &'static str {
91 if self.secure {
92 "https"
93 } else {
94 "http"
95 }
96 }
97}
98
99fn wide_slice(start: *const u16, length: u32) -> String {
100 if start.is_null() || length == 0 {
101 return String::new();
102 }
103 unsafe { String::from_utf16_lossy(core::slice::from_raw_parts(start, length as usize)) }
104}