Skip to main content

pacenotes_installer\http/
request_handle.rs

1/// `dwHeadersLength = -1`: the header string is NUL-terminated.
2const NUL_TERMINATED: u32 = u32::MAX;
3
4/// A WinHTTP handle, closed on drop so every early return unwinds the chain.
5struct Handle(*mut core::ffi::c_void);
6
7impl Drop for Handle {
8    fn drop(&mut self) {
9        if !self.0.is_null() {
10            unsafe { windows_sys::Win32::Networking::WinHttp::WinHttpCloseHandle(self.0) };
11        }
12    }
13}
14
15impl Handle {
16    fn checked(raw: *mut core::ffi::c_void, call: &str) -> Result<Handle, super::HttpError> {
17        if raw.is_null() {
18            Err(super::HttpError::last(call))
19        } else {
20            Ok(Handle(raw))
21        }
22    }
23}
24
25/// A sent request whose response headers have arrived; fields drop in
26/// declaration order, request before connection before session.
27pub struct OpenRequest {
28    request: Handle,
29    _connection: Handle,
30    _session: Handle,
31}
32
33impl OpenRequest {
34    /// `body`, when given, goes as `application/json`, the only type the installer posts.
35    pub fn send(
36        target: &super::url_target::UrlTarget,
37        method: &str,
38        bearer: Option<&str>,
39        body: Option<&[u8]>,
40    ) -> Result<OpenRequest, super::HttpError> {
41        let user_agent = crate::i18n::to_wide(super::USER_AGENT);
42        let session = Handle::checked(
43            unsafe {
44                windows_sys::Win32::Networking::WinHttp::WinHttpOpen(
45                    user_agent.as_ptr(),
46                    windows_sys::Win32::Networking::WinHttp::WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
47                    core::ptr::null(),
48                    core::ptr::null(),
49                    0,
50                )
51            },
52            "WinHttpOpen",
53        )?;
54        unsafe {
55            windows_sys::Win32::Networking::WinHttp::WinHttpSetTimeouts(
56                session.0,
57                super::TIMEOUT_RESOLVE_MS,
58                super::TIMEOUT_CONNECT_MS,
59                super::TIMEOUT_SEND_MS,
60                super::TIMEOUT_RECEIVE_MS,
61            )
62        };
63
64        let host = crate::i18n::to_wide(&target.host);
65        let connection = Handle::checked(
66            unsafe {
67                windows_sys::Win32::Networking::WinHttp::WinHttpConnect(
68                    session.0,
69                    host.as_ptr(),
70                    target.port,
71                    0,
72                )
73            },
74            "WinHttpConnect",
75        )?;
76
77        let verb = crate::i18n::to_wide(method);
78        let path = crate::i18n::to_wide(&target.path);
79        let flags = if target.secure {
80            windows_sys::Win32::Networking::WinHttp::WINHTTP_FLAG_SECURE
81        } else {
82            0
83        };
84        let request = Handle::checked(
85            unsafe {
86                windows_sys::Win32::Networking::WinHttp::WinHttpOpenRequest(
87                    connection.0,
88                    verb.as_ptr(),
89                    path.as_ptr(),
90                    core::ptr::null(),
91                    core::ptr::null(),
92                    core::ptr::null(),
93                    flags,
94                )
95            },
96            "WinHttpOpenRequest",
97        )?;
98        refuse_redirects(&request)?;
99
100        if let Some(token) = bearer {
101            let line = format!(
102                "{}: {}{token}",
103                pacenotes_shared::AUTH_HEADER,
104                pacenotes_shared::BEARER_PREFIX
105            );
106            add_header(&request, &line)?;
107        }
108        if body.is_some() {
109            add_header(&request, "Content-Type: application/json")?;
110        }
111        let payload = body.unwrap_or(&[]);
112        let payload_pointer = if body.is_some() {
113            payload.as_ptr() as *const core::ffi::c_void
114        } else {
115            core::ptr::null()
116        };
117        let sent = unsafe {
118            windows_sys::Win32::Networking::WinHttp::WinHttpSendRequest(
119                request.0,
120                core::ptr::null(),
121                0,
122                payload_pointer,
123                payload.len() as u32,
124                payload.len() as u32,
125                0,
126            )
127        };
128        if sent == 0 {
129            return Err(super::HttpError::last("WinHttpSendRequest"));
130        }
131        let received = unsafe {
132            windows_sys::Win32::Networking::WinHttp::WinHttpReceiveResponse(
133                request.0,
134                core::ptr::null_mut(),
135            )
136        };
137        if received == 0 {
138            return Err(super::HttpError::last("WinHttpReceiveResponse"));
139        }
140
141        Ok(OpenRequest {
142            request,
143            _connection: connection,
144            _session: session,
145        })
146    }
147
148    /// A numeric response header, such as the status code or content length.
149    pub fn query_number(&self, info: u32) -> Result<u32, super::HttpError> {
150        let mut value: u32 = 0;
151        let mut length = core::mem::size_of::<u32>() as u32;
152        let found = unsafe {
153            windows_sys::Win32::Networking::WinHttp::WinHttpQueryHeaders(
154                self.request.0,
155                info | windows_sys::Win32::Networking::WinHttp::WINHTTP_QUERY_FLAG_NUMBER,
156                core::ptr::null(),
157                &mut value as *mut u32 as *mut core::ffi::c_void,
158                &mut length,
159                core::ptr::null_mut(),
160            )
161        };
162        if found == 0 {
163            return Err(super::HttpError::last("WinHttpQueryHeaders"));
164        }
165        Ok(value)
166    }
167
168    /// A textual response header, or `None` when it is absent or its length
169    /// cannot be read.
170    pub fn query_text(&self, info: u32) -> Option<String> {
171        let mut length: u32 = 0;
172        unsafe {
173            windows_sys::Win32::Networking::WinHttp::WinHttpQueryHeaders(
174                self.request.0,
175                info,
176                core::ptr::null(),
177                core::ptr::null_mut(),
178                &mut length,
179                core::ptr::null_mut(),
180            )
181        };
182        // In bytes, with the NUL; less than one character means no such header.
183        if length < 2 {
184            return None;
185        }
186        let mut buffer: Vec<u16> = vec![0; length as usize / 2 + 1];
187        let found = unsafe {
188            windows_sys::Win32::Networking::WinHttp::WinHttpQueryHeaders(
189                self.request.0,
190                info,
191                core::ptr::null(),
192                buffer.as_mut_ptr() as *mut core::ffi::c_void,
193                &mut length,
194                core::ptr::null_mut(),
195            )
196        };
197        if found == 0 {
198            return None;
199        }
200        let characters = (length as usize / 2).min(buffer.len());
201        Some(String::from_utf16_lossy(&buffer[..characters]))
202    }
203
204    /// The next part of the body; 0 at its end.
205    pub fn read(&self, buffer: &mut [u8]) -> Result<usize, super::HttpError> {
206        let mut read: u32 = 0;
207        let succeeded = unsafe {
208            windows_sys::Win32::Networking::WinHttp::WinHttpReadData(
209                self.request.0,
210                buffer.as_mut_ptr() as *mut core::ffi::c_void,
211                buffer.len() as u32,
212                &mut read,
213            )
214        };
215        if succeeded == 0 {
216            return Err(super::HttpError::last("WinHttpReadData"));
217        }
218        Ok(read as usize)
219    }
220}
221
222/// WinHTTP's own following re-sends `Authorization` to the new origin unseen,
223/// so redirects come back to `send`; failing to set this would restore that
224/// default, so it is an error.
225fn refuse_redirects(request: &Handle) -> Result<(), super::HttpError> {
226    let never = windows_sys::Win32::Networking::WinHttp::WINHTTP_OPTION_REDIRECT_POLICY_NEVER;
227    let set = unsafe {
228        windows_sys::Win32::Networking::WinHttp::WinHttpSetOption(
229            request.0,
230            windows_sys::Win32::Networking::WinHttp::WINHTTP_OPTION_REDIRECT_POLICY,
231            &never as *const u32 as *const core::ffi::c_void,
232            core::mem::size_of::<u32>() as u32,
233        )
234    };
235    if set == 0 {
236        return Err(super::HttpError::last("disabling automatic redirects"));
237    }
238    Ok(())
239}
240
241fn add_header(request: &Handle, line: &str) -> Result<(), super::HttpError> {
242    let header = crate::i18n::to_wide(line);
243    let added = unsafe {
244        windows_sys::Win32::Networking::WinHttp::WinHttpAddRequestHeaders(
245            request.0,
246            header.as_ptr(),
247            NUL_TERMINATED,
248            windows_sys::Win32::Networking::WinHttp::WINHTTP_ADDREQ_FLAG_ADD,
249        )
250    };
251    if added == 0 {
252        return Err(super::HttpError::last("WinHttpAddRequestHeaders"));
253    }
254    Ok(())
255}