#[cfg(feature = "export")]
use crate::json;
#[cfg(feature = "export")]
use json::JsonValue;
use crate::error::HlsResult;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug)]
pub struct Cookie {
name: String,
value: String,
age: i32,
domain: String,
path: String,
http_only: bool,
secure: bool,
expires: String,
same_site: String,
icpsp: bool,
}
impl Default for Cookie {
fn default() -> Self {
Cookie {
name: "".to_string(),
value: "".to_string(),
age: -1,
domain: "".to_string(),
path: "".to_string(),
http_only: false,
secure: false,
expires: "".to_string(),
same_site: "".to_string(),
icpsp: false,
}
}
}
impl Cookie {
pub fn new_cookie(name: impl ToString, value: impl ToString) -> Cookie {
Cookie {
name: name.to_string(),
value: value.to_string(),
..Default::default()
}
}
pub fn insert(&mut self, k: &str, v: String) {
match k.to_lowercase().as_str() {
"httponly" => self.http_only = true,
"secure" => self.secure = true,
"path" => self.path = v,
"max-age" => self.age = v.parse().unwrap_or(-1),
"domain" => self.domain = v,
"expires" => self.expires = v,
"samesite" => self.same_site = v,
"icpsp" => self.icpsp = true,
_ => {
self.name = k.to_string();
self.value = v;
}
}
}
pub fn from_req(ck: impl AsRef<str>) -> HlsResult<Vec<Cookie>> {
let mut res = vec![];
let ck = ck.as_ref().replace("; ", ";");
for cookie in ck.split(";") {
let mut items = cookie.split("=");
let name = items.next().ok_or("cooke name not found")?.to_string();
let value = items.collect::<Vec<_>>().join("=");
res.push(Cookie::new_cookie(name, value));
}
Ok(res)
}
pub fn from_res(ck: impl AsRef<str>) -> HlsResult<Cookie> {
let mut cookie = Cookie::default();
let ck = ck.as_ref().replace("; ", ";");
for item in ck.split(";").filter(|x| x != &"") {
let mut items = item.split("=");
let name = items.next().ok_or("cooke name not found")?;
let value = items.next().unwrap_or("");
cookie.insert(name, value.to_string());
}
Ok(cookie)
}
pub fn as_res(&self) -> String {
let mut res = vec![format!("{}={}", self.name, self.value)];
if !self.expires.is_empty() { res.push(format!("expires={}", self.expires)); }
if self.age != -1 { res.push(format!("Max-Age={}", self.age)); }
if !self.path.is_empty() { res.push(format!("path={}", self.path)); }
if !self.same_site.is_empty() { res.push(format!("samesite={}", self.same_site)); }
if !self.domain.is_empty() { res.push(format!("domain={}", self.domain)); }
if self.secure { res.push("secure".to_string()); }
if self.http_only { res.push("httponly".to_string()); }
if self.icpsp { res.push("icpsp".to_string()); }
res.join("; ")
}
pub fn as_req(&self) -> String { format!("{}={}", self.name, self.value) }
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub fn set_value(&mut self, value: String) {
self.value = value;
}
pub fn set_age(&mut self, age: i32) {
self.age = age;
}
pub fn set_domain(&mut self, domain: String) {
self.domain = domain;
}
pub fn with_domain(mut self, domain: impl ToString) -> Self {
self.set_domain(domain.to_string());
self
}
pub fn with_expires(mut self, expires: impl ToString) -> Self {
self.set_expires(expires.to_string());
self
}
pub fn set_path(&mut self, path: String) {
self.path = path;
}
pub fn with_path(mut self, path: impl ToString) -> Self {
self.set_path(path.to_string());
self
}
pub fn set_http_only(&mut self, http_only: bool) {
self.http_only = http_only;
}
pub fn with_http_only(mut self, http_only: bool) -> Cookie {
self.set_http_only(http_only);
self
}
pub fn set_expires(&mut self, expires: String) {
self.expires = expires;
}
pub fn value(&self) -> &str {
&self.value
}
pub fn name(&self) -> &str {
&self.name
}
pub fn domain(&self) -> &str {
&self.domain
}
pub fn path(&self) -> &str {
&self.path
}
}
#[cfg(feature = "export")]
impl From<&Cookie> for JsonValue {
fn from(cookie: &Cookie) -> Self {
json::object! {
"name": cookie.name.clone(),
"value": cookie.value.clone(),
"age": cookie.age,
"domain": cookie.domain.clone(),
"path": cookie.path.clone(),
"http_only": cookie.http_only,
"secure": cookie.secure,
"expires": cookie.expires.clone(),
"same_site": cookie.same_site.clone(),
"icpsp": cookie.icpsp,
}
}
}
#[cfg(test)]
mod tests {
use crate::packet::http::cookie::Cookie;
#[test]
fn test_cookie() {
let cookie1 = "_EDGE_V=1; MUIDB=184C10AD397866DF1A1607B038566708; MUID=184C10AD397866DF1A1607B038566708; _UR=QS=0&TQS=0&Pn=0; BFBUSR=BFBHP=0; MUIDB=184C10AD397866DF1A1607B038566708; SRCHD=AF,AF,AF,AF,AF,AF,AF,AF,AF,AF,AF,AF,AF,AF,AF&AF=NOFORM; SRCHUID=V=2&GUID=EB7B9E5DE58F4D5690F6904732C24C7B&dmnchg=1; USRLOC=HS&ELOC=LAT=23.384721755981445|LON=113.44195556640625|N=%E7%99%BD%E4%BA%91%E5%8C%BA%EF%BC%8C%E5%B9%BF%E4%B8%9C%E7%9C%81|ELT=4|&HS=1; _RwBf=r&r&r&r&r=0&ilt=10&ihpd=5&ispd=3&rc=12&rb=0&rg=200&pc=12&mtu=0&rbb=0&clo=0&v=8&l=2026-03-15T07:00:00.0000000Z&lft=0001-01-01T00:00:00.0000000&aof=0&ard=0001-01-01T00:00:00.0000000&rwdbt=0&rwflt=0&rwaul2=0&g=&o=2&p=&c=&t=0&s=0001-01-01T00:00:00.0000000+00:00&ts=2026-03-15T14:03:35.7211444+00:00&rwred=0&wls=&wlb=&wle=&ccp=&cpt=&lka=0&lkt=0&aad=0&TH=&cid=0&gb=; SRCHUSR=DOB&DS&DS&DS&DS&DS=1&DOB=20260315; _EDGE_S=SID=357AA105805E678827ACB618817066E6; _SS=SID=357AA105805E678827ACB618817066E6; _HPVN=CS=eyJQbiI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiUCJ9LCJTYyI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiSCJ9LCJReiI6eyJDbiI6MSwiU3QiOjAsIlFzIjowLCJQcm9kIjoiVCJ9LCJBcCI6dHJ1ZSwiTXV0ZSI6dHJ1ZSwiTGFkIjoiMjAyNi0wMy0xNVQwMDowMDowMFoiLCJJb3RkIjowLCJHd2IiOjAsIlRucyI6MCwiRGZ0IjpudWxsLCJNdnMiOjAsIkZsdCI6MCwiSW1wIjozMCwiVG9ibiI6MH0=; SRCHHPGUSR=SRCHLANG&SRCHLANG&SRCHLANG&SRCHLANG&SRCHLANG&SRCHLANG&SRCHLANG&SRCHLANG&SRCHLANG&V&SRCHLANG&SRCHLANG&SRCHLANG&V&SRCHLANG&SRCHLANG=zh-Hans&PREFCOL=0&BRW=NOTP&BRH=M&CW=150&CH=769&SCW=150&SCH=769&DPR=1.0&UTC=480&HV=1773588648&HVE=CfDJ8HAK7eZCYw5BifHFeUHnkJGC6_lT8f9GeruXx8zjPXuk-5GHkofYMoFErMkT8CTKKKsSt5O2HyGmjLyCEXbEREUmwCd8ZBlYMLSDZu1wZ-EI1LDuyIiI1tkP6Usyicm601qX3aJVYqVWUBn-t6h0ZWLiftm4aS627xFj1fE5PD-85i7BWTkhqG0uvaYzuSgB2A&BZA=0&PRVCW=150&PRVCH=769&B=0&EXLTT=7&V=CfDJ8HAK7eZCYw5BifHFeUHnkJGijeRjCoaCMaAnmznMvdEg2GXY8647Wb-7wnHNpePKXRO6KRQ_0cQc-onivd35uV-p-4g0MB0V_Z1ZpW-QSJe9zbPUG-Ks-kQMjzEl6GlLo6N0ciP51vkQdR-P-lCUH58&PR=1";
let cookie = Cookie::from_req(cookie1).unwrap();
assert_eq!(cookie.len(), 15);
let cookie2 = "GC=Q4sdCza0cnj5G7P5IvdIbE5FSUS6b4z5A0SujitITnpD8uTkDt_q4kntWQnMCm-fXZCaGxTessBv0CNz94OaTA; expires=Fri, 19 Dec 2025 03:53:27 GMT; domain=.bing.com; path=/; secure; samesite=none";
let cookie = Cookie::from_res(cookie2).unwrap();
assert_eq!(cookie.name(), "GC");
assert_eq!(cookie.age, -1);
assert_eq!(cookie.domain, ".bing.com");
assert_eq!(cookie.path, "/");
assert!(!cookie.http_only);
assert!(cookie.secure);
assert_eq!(cookie.expires, "Fri, 19 Dec 2025 03:53:27 GMT")
}
}