Skip to main content

pacenotes_installer\http/
http_error.rs

1const CANCELLED: &str = "cancelled";
2
3pub struct HttpError {
4    /// The WinHTTP or `GetLastError` code; 0 for failures with no OS code.
5    pub code: u32,
6    /// Readable as-is in a MessageBox or the log.
7    pub message: String,
8    /// Set when a response arrived, so 401/403 can be told from "unreachable".
9    pub status: Option<u16>,
10}
11
12impl HttpError {
13    /// Renders `GetLastError` immediately, while the thread's error is still ours.
14    pub(super) fn last(context: &str) -> HttpError {
15        let code = unsafe { windows_sys::Win32::Foundation::GetLastError() };
16        HttpError {
17            code,
18            message: format!("{context}: {} (0x{code:08X})", error_text(code)),
19            status: None,
20        }
21    }
22
23    /// A reachable server sent a body that could not be understood, which a retry will not fix.
24    pub fn parse(message: impl Into<String>) -> HttpError {
25        HttpError::logical(message, None)
26    }
27
28    pub(super) fn logical(message: impl Into<String>, status: Option<u16>) -> HttpError {
29        HttpError {
30            code: 0,
31            message: message.into(),
32            status,
33        }
34    }
35
36    pub fn cancelled() -> HttpError {
37        HttpError::logical(CANCELLED, None)
38    }
39
40    /// The caller asked to stop, which is not a failure to report.
41    pub fn is_cancelled(&self) -> bool {
42        self.code == 0 && self.status.is_none() && self.message == CANCELLED
43    }
44
45    /// Whether trying again could succeed: no response at all, or one that
46    /// says the server is busy or broken. A 401 or 404 will not change.
47    pub fn is_transient(&self) -> bool {
48        match self.status {
49            Some(status) => status == 408 || status == 429 || status >= 500,
50            None => self.code != 0,
51        }
52    }
53}
54
55/// WinHTTP's own codes (12001+) live in winhttp.dll's message table, not the
56/// system's, so it must be named or every network failure is a bare number.
57fn error_text(code: u32) -> String {
58    let module = unsafe {
59        windows_sys::Win32::System::LibraryLoader::GetModuleHandleW(
60            crate::i18n::to_wide("winhttp.dll").as_ptr(),
61        )
62    };
63    let mut buffer = [0u16; 512];
64    let length = unsafe {
65        windows_sys::Win32::System::Diagnostics::Debug::FormatMessageW(
66            windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_FROM_SYSTEM
67                | windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_FROM_HMODULE
68                | windows_sys::Win32::System::Diagnostics::Debug::FORMAT_MESSAGE_IGNORE_INSERTS,
69            module as *const core::ffi::c_void,
70            code,
71            0,
72            buffer.as_mut_ptr(),
73            buffer.len() as u32,
74            core::ptr::null(),
75        )
76    };
77    if length == 0 {
78        return "unknown error".into();
79    }
80    String::from_utf16_lossy(&buffer[..length as usize])
81        .trim_end_matches(['\r', '\n', ' '])
82        .to_string()
83}