#![deny(
clippy::cast_slice_from_raw_parts,
clippy::cast_slice_different_sizes,
clippy::invalid_null_ptr_usage,
clippy::ptr_as_ptr,
clippy::transmute_ptr_to_ref
)]
use proj_sys::{proj_context_set_network_callbacks, PJ_CONTEXT, PROJ_NETWORK_HANDLE};
use std::collections::HashMap;
use std::ffi::CString;
use std::io::Read;
use std::ops::Range;
use std::os::raw::c_ulonglong;
use std::ptr::{self, NonNull};
use ureq::Agent;
use crate::proj::{ProjError, _string};
use libc::c_char;
use libc::c_void;
use std::boxed::Box;
use std::{thread, time};
const CLIENT: &str = concat!("proj-rs/", env!("CARGO_PKG_VERSION"));
const MAX_RETRIES: u8 = 8;
const RETRY_CODES: [u16; 4] = [429, 500, 502, 504];
const SUCCESS_ERROR_CODES: Range<u16> = 200..300;
const CLIENT_ERROR_CODES: Range<u16> = 400..500;
const SERVER_ERROR_CODES: Range<u16> = 500..600;
struct HandleData {
url: String,
headers: HashMap<String, String>,
hptr: Option<NonNull<c_char>>,
}
impl HandleData {
fn new(url: String, headers: HashMap<String, String>, hptr: Option<NonNull<c_char>>) -> Self {
Self { url, headers, hptr }
}
}
impl Drop for HandleData {
fn drop(&mut self) {
if let Some(header) = self.hptr {
let _ = unsafe { CString::from_raw(header.as_ptr().cast()) };
}
}
}
fn get_wait_time_quad(retrycount: i32) -> u64 {
if retrycount == 0 {
return 0;
}
(retrycount as u64).pow(2) * 100u64
}
fn error_handler<'a>(
res: &'a mut http::Response<ureq::Body>,
url: &str,
headers: &[(&str, &str)],
clt: Agent,
) -> Result<&'a mut http::Response<ureq::Body>, ProjError> {
let mut retries = 0;
if SERVER_ERROR_CODES.contains(&res.status().as_u16())
|| RETRY_CODES.contains(&res.status().as_u16())
{
while (SERVER_ERROR_CODES.contains(&res.status().as_u16())
|| RETRY_CODES.contains(&res.status().as_u16()))
&& retries <= MAX_RETRIES
{
retries += 1;
let wait = time::Duration::from_millis(get_wait_time_quad(retries as i32));
thread::sleep(wait);
let mut req = clt.get(url);
for (name, value) in headers {
req = req.header(*name, *value);
}
*res = req.call()?;
}
} else if CLIENT_ERROR_CODES.contains(&res.status().as_u16()) {
return Err(ProjError::DownloadError(
res.status().to_string(),
url.to_string(),
retries,
));
}
if !SUCCESS_ERROR_CODES.contains(&res.status().as_u16()) {
return Err(ProjError::DownloadError(
res.status().to_string(),
url.to_string(),
retries,
));
}
Ok(res)
}
pub(crate) unsafe extern "C" fn network_open(
pc: *mut PJ_CONTEXT,
url: *const c_char,
offset: c_ulonglong,
size_to_read: usize,
buffer: *mut c_void,
out_size_read: *mut usize,
error_string_max_size: usize,
out_error_string: *mut c_char,
ud: *mut c_void,
) -> *mut PROJ_NETWORK_HANDLE {
match _network_open(
pc,
url,
offset,
size_to_read,
buffer,
out_size_read,
error_string_max_size,
out_error_string,
ud,
) {
Ok(res) => res,
#[allow(clippy::ptr_as_ptr)]
Err(e) => {
let err_string = e.to_string();
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), err_string.len());
out_error_string.add(err_string.len()).write(0);
ptr::null_mut()
}
}
}
#[allow(clippy::too_many_arguments)]
unsafe fn _network_open(
_: *mut PJ_CONTEXT,
url: *const c_char,
offset: c_ulonglong,
size_to_read: usize,
buffer: *mut c_void,
out_size_read: *mut usize,
_: usize,
out_error_string: *mut c_char,
_: *mut c_void,
) -> Result<*mut PROJ_NETWORK_HANDLE, ProjError> {
let url = _string(url)?;
let end = offset as usize + size_to_read - 1;
let hvalue = format!("bytes={offset}-{end}");
let clt = Agent::new_with_defaults();
let req = clt
.get(&url)
.header("Range", &hvalue)
.header("Client", CLIENT);
let mut res = req.call()?;
let headers = [("Range", hvalue.as_str()), ("Client", CLIENT)];
error_handler(&mut res, &url, &headers, clt.clone())?;
let contentlength = res
.headers()
.get("Content-Length")
.and_then(|val| val.to_str().ok())
.and_then(|s| s.parse::<usize>().ok())
.ok_or(ProjError::ContentLength)?;
let headers = res
.headers()
.iter()
.filter_map(|(h, v)| {
let header_name = h.to_string();
let header_value = v.to_str().ok()?.to_string();
Some((header_name, header_value))
})
.collect();
let capacity = contentlength.min(size_to_read);
let mut buf = Vec::<u8>::with_capacity(capacity);
let body_reader = res.body_mut().as_reader();
body_reader
.take(size_to_read as u64)
.read_to_end(&mut buf)?;
out_size_read.write(buf.len());
buf.as_ptr().copy_to_nonoverlapping(buffer.cast(), capacity);
let hd = HandleData::new(url, headers, None);
let hd_boxed = Box::new(hd);
let void: *mut c_void = Box::into_raw(hd_boxed).cast::<libc::c_void>();
let opaque: *mut PROJ_NETWORK_HANDLE = void.cast::<proj_sys::PROJ_NETWORK_HANDLE>();
let err_string = "";
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), err_string.len());
out_error_string.add(err_string.len()).write(0);
Ok(opaque)
}
pub(crate) unsafe extern "C" fn network_close(
_: *mut PJ_CONTEXT,
handle: *mut PROJ_NETWORK_HANDLE,
_: *mut c_void,
) {
let void = handle.cast::<libc::c_void>();
let hd = void.cast::<HandleData>();
let _: Box<HandleData> = Box::from_raw(hd);
}
pub(crate) unsafe extern "C" fn network_get_header_value(
pc: *mut PJ_CONTEXT,
handle: *mut PROJ_NETWORK_HANDLE,
header_name: *const c_char,
ud: *mut c_void,
) -> *const c_char {
let hd = &mut *(handle as *const c_void as *mut HandleData);
match _network_get_header_value(pc, handle, header_name, ud) {
Ok(res) => res,
Err(_) => {
let hvalue = "";
let cstr = CString::new(hvalue).unwrap();
let err = cstr.into_raw();
hd.hptr = Some(NonNull::new(err).expect("Failed to create non-Null pointer"));
err
}
}
}
unsafe fn _network_get_header_value(
_: *mut PJ_CONTEXT,
handle: *mut PROJ_NETWORK_HANDLE,
header_name: *const c_char,
_: *mut c_void,
) -> Result<*const c_char, ProjError> {
let lookup = _string(header_name)?.to_lowercase();
let void = handle.cast::<libc::c_void>();
let hd = &mut *(void.cast::<HandleData>());
let hvalue = hd
.headers
.get(&lookup)
.ok_or_else(|| ProjError::HeaderError(lookup.to_string()))?;
let cstr = CString::new(&**hvalue).unwrap();
let header = cstr.into_raw();
hd.hptr = Some(
NonNull::new(header).expect("Failed to create non-Null pointer when building header value"),
);
Ok(header)
}
pub(crate) unsafe extern "C" fn network_read_range(
pc: *mut PJ_CONTEXT,
handle: *mut PROJ_NETWORK_HANDLE,
offset: c_ulonglong,
size_to_read: usize,
buffer: *mut c_void,
error_string_max_size: usize,
out_error_string: *mut c_char,
ud: *mut c_void,
) -> usize {
match _network_read_range(
pc,
handle,
offset,
size_to_read,
buffer,
error_string_max_size,
out_error_string,
ud,
) {
Ok(res) => res,
Err(e) => {
let err_string = e.to_string().replace('0', "nought");
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), err_string.len());
out_error_string.add(err_string.len()).write(0);
0usize
}
}
}
#[allow(clippy::too_many_arguments)]
fn _network_read_range(
_: *mut PJ_CONTEXT,
handle: *mut PROJ_NETWORK_HANDLE,
offset: c_ulonglong,
size_to_read: usize,
buffer: *mut c_void,
_: usize,
out_error_string: *mut c_char,
_: *mut c_void,
) -> Result<usize, ProjError> {
let end = offset as usize + size_to_read - 1;
let hvalue = format!("bytes={offset}-{end}");
let hd = unsafe { &mut *(handle as *const c_void as *mut HandleData) };
let clt = Agent::new_with_defaults();
let req = clt
.get(&hd.url)
.header("Range", &hvalue)
.header("Client", CLIENT);
let mut res = req.call()?;
let headers = [("Range", hvalue.as_str()), ("Client", CLIENT)];
error_handler(&mut res, &hd.url, &headers, clt.clone())?;
let headers = res
.headers()
.iter()
.filter_map(|(h, v)| {
let header_name = h.to_string();
let header_value = v.to_str().ok()?.to_string();
Some((header_name, header_value))
})
.collect();
let contentlength = res
.headers()
.get("Content-Length")
.and_then(|val| val.to_str().ok())
.and_then(|s| s.parse::<usize>().ok())
.ok_or(ProjError::ContentLength)?;
let capacity = contentlength.min(size_to_read);
let mut buf = Vec::<u8>::with_capacity(capacity);
let body_reader = res.body_mut().as_reader();
body_reader
.take(size_to_read as u64)
.read_to_end(&mut buf)?;
unsafe {
buf.as_ptr()
.copy_to_nonoverlapping(buffer.cast::<u8>(), capacity);
}
let err_string = "";
unsafe {
out_error_string.copy_from_nonoverlapping(err_string.as_ptr().cast(), err_string.len());
out_error_string.add(err_string.len()).write(0);
}
hd.headers = headers;
Ok(buf.len())
}
pub(crate) fn set_network_callbacks(ctx: *mut PJ_CONTEXT) -> i32 {
let ud: *mut c_void = ptr::null_mut();
unsafe {
proj_context_set_network_callbacks(
ctx,
Some(network_open),
Some(network_close),
Some(network_get_header_value),
Some(network_read_range),
ud,
)
}
}