use std::ptr::NonNull;
use windows_sys::Win32::Networking::WinHttp::*;
use crate::error::{Result, WinHttpError};
#[derive(Debug)]
pub(crate) struct SessionHandle {
handle: NonNull<std::ffi::c_void>,
}
unsafe impl Send for SessionHandle {}
unsafe impl Sync for SessionHandle {}
impl SessionHandle {
pub(crate) fn new(
user_agent: Option<&str>,
is_async: bool,
use_default_proxy: bool,
) -> Result<Self> {
let user_agent_wide: Vec<u16>;
let user_agent_ptr = match user_agent {
Some(ua) => {
user_agent_wide = ua.encode_utf16().chain(std::iter::once(0)).collect();
user_agent_wide.as_ptr()
}
None => std::ptr::null(),
};
let access_type = if use_default_proxy {
WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY
} else {
WINHTTP_ACCESS_TYPE_NO_PROXY
};
let flags = if is_async { WINHTTP_FLAG_ASYNC } else { 0 };
let handle = unsafe {
WinHttpOpen(
user_agent_ptr,
access_type,
std::ptr::null(),
std::ptr::null(),
flags,
)
};
NonNull::new(handle)
.map(|handle| Self { handle })
.ok_or_else(|| WinHttpError::from_last_error("WinHttpOpen"))
}
#[inline]
pub(crate) fn as_raw(&self) -> *mut std::ffi::c_void {
self.handle.as_ptr()
}
pub(crate) fn set_timeouts(
&self,
resolve_timeout: i32,
connect_timeout: i32,
send_timeout: i32,
receive_timeout: i32,
) -> Result<()> {
let result = unsafe {
WinHttpSetTimeouts(
self.as_raw(),
resolve_timeout,
connect_timeout,
send_timeout,
receive_timeout,
)
};
if result == 0 {
return Err(WinHttpError::from_last_error("WinHttpSetTimeouts"));
}
Ok(())
}
pub(crate) unsafe fn set_option<T>(
&self,
option: u32,
value: &T,
error_context: &'static str,
) -> Result<()> {
let Ok(buffer_len) = u32::try_from(std::mem::size_of::<T>()) else {
panic!("{error_context} Option {option} value is too large to fit in u32 length");
};
let result = unsafe {
WinHttpSetOption(
self.as_raw(),
option,
value as *const T as *const std::ffi::c_void,
buffer_len,
)
};
if result == 0 {
return Err(WinHttpError::from_last_error(error_context));
}
Ok(())
}
pub(crate) fn disable_redirects(&self) -> Result<()> {
let policy: u32 = WINHTTP_OPTION_REDIRECT_POLICY_NEVER;
unsafe {
self.set_option(
WINHTTP_OPTION_REDIRECT_POLICY,
&policy,
"WinHttpSetOption (disable_redirects)",
)
}
}
pub(crate) fn enable_redirects(&self) -> Result<()> {
let policy: u32 = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS;
unsafe {
self.set_option(
WINHTTP_OPTION_REDIRECT_POLICY,
&policy,
"WinHttpSetOption (enable_redirects)",
)
}
}
pub(crate) fn set_receive_response_timeout(&self, timeout_ms: u32) -> Result<()> {
unsafe {
self.set_option(
WINHTTP_OPTION_RECEIVE_RESPONSE_TIMEOUT,
&timeout_ms,
"WinHttpSetOption (set_receive_response_timeout)",
)
}
}
pub(crate) unsafe fn set_status_callback(
&self,
callback: WINHTTP_STATUS_CALLBACK,
notification_flags: u32,
) -> Result<WINHTTP_STATUS_CALLBACK> {
let prev = WinHttpSetStatusCallback(self.as_raw(), callback, notification_flags, 0);
if unsafe { std::mem::transmute::<WINHTTP_STATUS_CALLBACK, usize>(prev) } == usize::MAX {
let error = windows_sys::Win32::Foundation::GetLastError();
return Err(WinHttpError::from_code(error, "WinHttpSetStatusCallback"));
}
Ok(prev)
}
}
impl Drop for SessionHandle {
fn drop(&mut self) {
unsafe {
WinHttpCloseHandle(self.as_raw());
}
}
}