use std::time::Instant;
use crate::ffi::models::Limits;
use crate::ffi::errors::{ErrorHandle, Status};
use crate::ffi::{Buffer, Slice};
use crate::cookies::{Cookie, CookieJar, SameSite, SetCookie};
use crate::models::URL;
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_cookie_new() -> *mut Cookie {
Box::into_raw(Box::new(Cookie::new()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookie_parse(value: *const u8, value_len: usize) -> *mut Cookie {
match unsafe { Slice::borrow_text(value, value_len) } {
Some(value) => Box::into_raw(Box::new(Cookie::parse(value))),
None => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookie_free(cookie: *mut Cookie) {
if !cookie.is_null() {
drop(unsafe { Box::from_raw(cookie) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookie_count(cookie: *const Cookie) -> usize {
unsafe { cookie.as_ref() }.map_or(0, |cookie| cookie.pairs.len())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookie_name(cookie: *const Cookie, index: usize) -> Slice {
Slice::maybe(unsafe { cookie.as_ref() }.and_then(|cookie| cookie.pairs.get(index)).map(|(name, _)| name.as_str()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookie_value(cookie: *const Cookie, index: usize) -> Slice {
Slice::maybe(unsafe { cookie.as_ref() }.and_then(|cookie| cookie.pairs.get(index)).map(|(_, value)| value.as_str()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookie_get(cookie: *const Cookie, name: *const u8, name_len: usize) -> Slice {
let Some(name) = (unsafe { Slice::borrow_text(name, name_len) }) else {
return Slice::ABSENT;
};
Slice::maybe(unsafe { cookie.as_ref() }.and_then(|cookie| cookie.get(name)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookie_append(cookie: *mut Cookie, name: *const u8, name_len: usize, value: *const u8, value_len: usize) -> bool {
let (Some(cookie), Some(name), Some(value)) = (unsafe { cookie.as_mut() }, unsafe { Slice::borrow_text(name, name_len) }, unsafe { Slice::borrow_text(value, value_len) })
else {
return false;
};
cookie.pairs.push((name.to_owned(), value.to_owned()));
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookie_build(cookie: *const Cookie) -> Buffer {
match unsafe { cookie.as_ref() } {
Some(cookie) => Buffer::new(cookie.build().into_bytes()),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_new(name: *const u8, name_len: usize, value: *const u8, value_len: usize) -> *mut SetCookie {
let (Some(name), Some(value)) = (unsafe { Slice::borrow_text(name, name_len) }, unsafe { Slice::borrow_text(value, value_len) }) else {
return std::ptr::null_mut();
};
Box::into_raw(Box::new(SetCookie::new(name, value)))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_parse(value: *const u8, value_len: usize, out: *mut *mut SetCookie, error: *mut *mut ErrorHandle) -> Status {
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
let Some(value) = (unsafe { Slice::borrow_text(value, value_len) }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
match SetCookie::parse(value) {
Ok(cookie) => {
unsafe { *out = Box::into_raw(Box::new(cookie)) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_free(cookie: *mut SetCookie) {
if !cookie.is_null() {
drop(unsafe { Box::from_raw(cookie) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_name(cookie: *const SetCookie) -> Slice {
Slice::maybe(unsafe { cookie.as_ref() }.map(|cookie| cookie.name.as_str()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_value(cookie: *const SetCookie) -> Slice {
Slice::maybe(unsafe { cookie.as_ref() }.map(|cookie| cookie.value.as_str()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_expires(cookie: *const SetCookie) -> Slice {
Slice::maybe(unsafe { cookie.as_ref() }.and_then(|cookie| cookie.expires.as_deref()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_max_age(cookie: *const SetCookie, out: *mut i64) -> bool {
let Some(max_age) = unsafe { cookie.as_ref() }.and_then(|cookie| cookie.max_age) else {
return false;
};
if !out.is_null() {
unsafe { *out = max_age };
}
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_domain(cookie: *const SetCookie) -> Slice {
Slice::maybe(unsafe { cookie.as_ref() }.and_then(|cookie| cookie.domain.as_deref()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_path(cookie: *const SetCookie) -> Slice {
Slice::maybe(unsafe { cookie.as_ref() }.and_then(|cookie| cookie.path.as_deref()))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_secure(cookie: *const SetCookie) -> bool {
unsafe { cookie.as_ref() }.is_some_and(|cookie| cookie.secure)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_httponly(cookie: *const SetCookie) -> bool {
unsafe { cookie.as_ref() }.is_some_and(|cookie| cookie.httponly)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_samesite(cookie: *const SetCookie) -> i32 {
match unsafe { cookie.as_ref() }.and_then(|cookie| cookie.samesite) {
Some(SameSite::Strict) => 0,
Some(SameSite::Lax) => 1,
Some(SameSite::None) => 2,
None => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_set_value(cookie: *mut SetCookie, value: *const u8, value_len: usize) -> bool {
let (Some(cookie), Some(value)) = (unsafe { cookie.as_mut() }, unsafe { Slice::borrow_text(value, value_len) }) else {
return false;
};
cookie.value = value.to_owned();
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_set_expires(cookie: *mut SetCookie, value: *const u8, value_len: usize) -> bool {
let Some(cookie) = (unsafe { cookie.as_mut() }) else {
return false;
};
if value.is_null() {
cookie.expires = None;
return true;
}
let Some(value) = (unsafe { Slice::borrow_text(value, value_len) }) else {
return false;
};
cookie.expires = Some(value.to_owned());
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_set_max_age(cookie: *mut SetCookie, present: bool, max_age: i64) -> bool {
let Some(cookie) = (unsafe { cookie.as_mut() }) else {
return false;
};
cookie.max_age = present.then_some(max_age);
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_set_domain(cookie: *mut SetCookie, value: *const u8, value_len: usize) -> bool {
let Some(cookie) = (unsafe { cookie.as_mut() }) else {
return false;
};
if value.is_null() {
cookie.domain = None;
return true;
}
let Some(value) = (unsafe { Slice::borrow_text(value, value_len) }) else {
return false;
};
cookie.domain = Some(value.to_owned());
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_set_path(cookie: *mut SetCookie, value: *const u8, value_len: usize) -> bool {
let Some(cookie) = (unsafe { cookie.as_mut() }) else {
return false;
};
if value.is_null() {
cookie.path = None;
return true;
}
let Some(value) = (unsafe { Slice::borrow_text(value, value_len) }) else {
return false;
};
cookie.path = Some(value.to_owned());
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_set_secure(cookie: *mut SetCookie, secure: bool) -> bool {
let Some(cookie) = (unsafe { cookie.as_mut() }) else {
return false;
};
cookie.secure = secure;
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_set_httponly(cookie: *mut SetCookie, httponly: bool) -> bool {
let Some(cookie) = (unsafe { cookie.as_mut() }) else {
return false;
};
cookie.httponly = httponly;
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_set_samesite(cookie: *mut SetCookie, samesite: i32) -> bool {
let Some(cookie) = (unsafe { cookie.as_mut() }) else {
return false;
};
cookie.samesite = match samesite {
0 => Some(SameSite::Strict),
1 => Some(SameSite::Lax),
2 => Some(SameSite::None),
-1 => None,
_ => return false,
};
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_build(cookie: *const SetCookie, out: *mut Buffer, error: *mut *mut ErrorHandle) -> Status {
let Some(cookie) = (unsafe { cookie.as_ref() }) else {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
};
if out.is_null() {
return unsafe { ErrorHandle::raise(error, Status::Invalid) };
}
match cookie.build() {
Ok(value) => {
unsafe { *out = Buffer::new(value.into_bytes()) };
Status::Ok
}
Err(failure) => unsafe { ErrorHandle::report(error, &failure) },
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookiejar_new(limits: *const Limits) -> *mut CookieJar {
Box::into_raw(Box::new(CookieJar::new().with_limits(unsafe { Limits::or_default(limits) })))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookiejar_free(jar: *mut CookieJar) {
if !jar.is_null() {
drop(unsafe { Box::from_raw(jar) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookiejar_learn(jar: *const CookieJar, url: *const URL, values: *const Slice, value_count: usize) -> bool {
let (Some(jar), Some(url)) = (unsafe { jar.as_ref() }, unsafe { url.as_ref() }) else {
return false;
};
if values.is_null() && value_count > 0 {
return false;
}
let mut parsed = Vec::with_capacity(value_count);
for index in 0..value_count {
let slice = unsafe { *values.add(index) };
let Some(value) = (unsafe { Slice::borrow_text(slice.data, slice.len) }) else {
return false;
};
parsed.push(value);
}
jar.learn(url, &parsed, Instant::now());
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookiejar_cookie(jar: *const CookieJar, url: *const URL) -> Buffer {
let (Some(jar), Some(url)) = (unsafe { jar.as_ref() }, unsafe { url.as_ref() }) else {
return Buffer::EMPTY;
};
match jar.cookie(url, Instant::now()) {
Some(value) => Buffer::new(value.into_bytes()),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookiejar_prune(jar: *const CookieJar) {
if let Some(jar) = unsafe { jar.as_ref() } {
jar.prune(Instant::now());
}
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_cookie_default_max_cookies() -> u32 {
crate::cookies::CookieLimits::default().max_cookies
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_cookie_default_max_cookies_per_domain() -> u16 {
crate::cookies::CookieLimits::default().max_cookies_per_domain
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_cookie_is_separator(octet: u8) -> bool {
Cookie::is_separator(octet)
}
#[unsafe(no_mangle)]
pub extern "C" fn soyokaze_samesite_name(samesite: i32) -> Slice {
match SameSite::from_code(samesite) {
Some(samesite) => Slice::text(samesite.as_str()),
None => Slice::ABSENT,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_samesite_parse(name: *const u8, name_len: usize) -> i32 {
match unsafe { Slice::borrow_text(name, name_len) }.and_then(SameSite::parse) {
Some(samesite) => samesite as i32,
None => -1,
}
}
impl SameSite {
pub fn from_code(code: i32) -> Option<Self> {
Some(match code {
0 => Self::Strict,
1 => Self::Lax,
2 => Self::None,
_ => return None,
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_setcookie_age(text: *const u8, text_len: usize, out: *mut i64) -> bool {
let Some(age) = unsafe { Slice::borrow_text(text, text_len) }.and_then(SetCookie::age) else {
return false;
};
if !out.is_null() {
unsafe { *out = age };
}
true
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookie_path_matches(target: *const u8, target_len: usize, path: *const u8, path_len: usize) -> bool {
let (Some(target), Some(path)) = (unsafe { Slice::borrow_text(target, target_len) }, unsafe { Slice::borrow_text(path, path_len) }) else {
return false;
};
crate::cookies::StoredCookie::path_matches(target, path)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookie_default_path(target: *const u8, target_len: usize) -> Buffer {
match unsafe { Slice::borrow_text(target, target_len) } {
Some(target) => Buffer::new(crate::cookies::StoredCookie::default_path(target).into_bytes()),
None => Buffer::EMPTY,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookiejar_count(jar: *const CookieJar) -> usize {
match unsafe { jar.as_ref() } {
Some(jar) => crate::helpers::sync::Lock::on(&jar.entries).len(),
None => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookiejar_max_cookies(jar: *const CookieJar) -> u32 {
unsafe { jar.as_ref() }.map_or(0, |jar| jar.limits.max_cookies)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookiejar_max_cookies_per_domain(jar: *const CookieJar) -> u16 {
unsafe { jar.as_ref() }.map_or(0, |jar| jar.limits.max_cookies_per_domain)
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct StoredCookie {
pub name: Slice,
pub value: Slice,
pub domain: Slice,
pub host_only: bool,
pub path: Slice,
pub secure: bool,
pub expires_in: f64,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn soyokaze_cookiejar_entry(jar: *const CookieJar, index: usize, out: *mut StoredCookie, storage: *mut Buffer) -> bool {
let Some(jar) = (unsafe { jar.as_ref() }) else {
return false;
};
let entries = crate::helpers::sync::Lock::on(&jar.entries);
let Some(entry) = entries.get(index) else {
return false;
};
let mut octets = Vec::with_capacity(entry.name.len() + entry.value.len() + entry.domain.len() + entry.path.len());
let mut spans = Vec::with_capacity(4);
for part in [&entry.name, &entry.value, &entry.domain, &entry.path] {
spans.push((octets.len(), part.len()));
octets.extend_from_slice(part.as_bytes());
}
let expires_in = match entry.expires {
Some(expires) => expires.saturating_duration_since(Instant::now()).as_secs_f64(),
None => -1.0,
};
let buffer = Buffer::new(octets);
let view = |(at, len): (usize, usize)| Slice { data: unsafe { buffer.data.add(at) }, len };
if !out.is_null() {
unsafe {
*out = StoredCookie {
name: view(spans[0]),
value: view(spans[1]),
domain: view(spans[2]),
host_only: entry.host_only,
path: view(spans[3]),
secure: entry.secure,
expires_in,
}
};
}
match storage.is_null() {
true => unsafe { crate::ffi::soyokaze_buffer_free(buffer) },
false => unsafe { *storage = buffer },
}
true
}