nescookie/lib.rs
1#![allow(clippy::tabs_in_doc_comments)]
2
3pub mod error;
4
5use crate::error::Error;
6pub use cookie::{Cookie, CookieJar};
7use error::ParseError;
8use std::{
9 fs::File,
10 io::{BufRead, BufReader},
11 path::Path,
12};
13pub use time::OffsetDateTime;
14
15/// A netscape cookie parser
16/// allowing generating a new [`CookieJar`](cookie::CookieJar) or writing to an exist one.
17#[derive(Debug, Default)]
18pub struct CookieJarBuilder {
19 jar: CookieJar,
20}
21
22impl CookieJarBuilder {
23 /// Creates a new `CookieJarBuilder`
24 /// ```
25 /// use nescookie::CookieJarBuilder;
26 ///
27 /// let jar = CookieJarBuilder::new().finish();
28 /// ```
29 pub fn new() -> Self {
30 Self::default()
31 }
32 /// Creates a new `CookieJarBuilder` from a [`CookieJar`](cookie::CookieJar)
33 /// parsed cookies will be added to it
34 pub fn with_jar(jar: CookieJar) -> Self {
35 Self { jar }
36 }
37 /// Opens a file with `path` and parses it as cookies
38 ///
39 /// ```
40 /// use nescookie::CookieJarBuilder;
41 ///
42 /// let jar = CookieJarBuilder::new().open("tests/cookies.txt").unwrap().finish();
43 /// ```
44 pub fn open(self, path: impl AsRef<Path>) -> Result<Self, Error> {
45 self.parse_buffer(BufReader::new(File::open(path)?))
46 }
47 /// Parses cookies from something that implements [`BufRead`](std::io::BufRead)
48 ///
49 /// ```
50 /// use nescookie::CookieJarBuilder;
51 /// use std::io::Cursor;
52 ///
53 /// let buf = Cursor::new(b".pixiv.net TRUE / TRUE 1784339332 p_ab_id 7\n");
54 /// let jar = CookieJarBuilder::new().parse_buffer(buf).unwrap().finish();
55 /// ```
56 pub fn parse_buffer(self, mut buf: impl BufRead) -> Result<Self, Error> {
57 let mut s = String::new();
58 buf.read_to_string(&mut s)?;
59 self.parse(&s)
60 }
61 /// Parses cookies from an str
62 ///
63 /// ```
64 /// use nescookie::CookieJarBuilder;
65 ///
66 /// let content = ".pixiv.net TRUE / TRUE 1784339332 p_ab_id 7\n";
67 /// let jar = CookieJarBuilder::new().parse(content).unwrap().finish();
68 /// ```
69 pub fn parse(mut self, s: &str) -> Result<Self, Error> {
70 // todo: check if there is a newline before eof
71 for c in s.lines().map(|s| s.trim()).filter(|s| !s.is_empty()) {
72 let (http_only, mut fileds) = if c.starts_with('#') {
73 if c.starts_with("#HttpOnly_") {
74 (true, c.trim_start_matches("#HttpOnly_").split('\t'))
75 } else {
76 continue;
77 }
78 } else {
79 (false, c.split('\t'))
80 };
81 let domain = fileds.next().ok_or(ParseError::TooFewFileds)?;
82 let _ = fileds.next(); // ignore subdomain
83 let path = fileds.next().ok_or(ParseError::TooFewFileds)?;
84 let secure = match fileds.next().ok_or(ParseError::TooFewFileds)? {
85 "TRUE" => true,
86 "FALSE" => false,
87 value => return Err(ParseError::InvaildValue(value.to_owned()).into()),
88 };
89 let expiration: i64 = match fileds.next() {
90 Some(value) => match value.parse() {
91 Ok(v) => v,
92 Err(_) => return Err(ParseError::InvaildValue(value.to_owned()).into()),
93 },
94 _ => return Err(ParseError::TooFewFileds.into()),
95 };
96 let name = fileds.next().ok_or(ParseError::TooFewFileds)?;
97 let value = fileds.next().ok_or(ParseError::TooFewFileds)?;
98 let cookie = Cookie::build(name, value)
99 .domain(domain)
100 .path(path)
101 .secure(secure)
102 .expires(match expiration {
103 0 => None,
104 exp => Some(OffsetDateTime::from_unix_timestamp(exp)),
105 });
106 let cookie = if http_only {
107 cookie.http_only(true).finish()
108 } else {
109 cookie.finish()
110 };
111 self.jar.add(cookie.into_owned());
112 }
113 Ok(self)
114 }
115 /// Returns the built `CookieJar`
116 pub fn finish(self) -> CookieJar {
117 self.jar
118 }
119}
120
121/// Opens a file with `path` and parses it as [`CookieJar`](cookie::CookieJar)
122///
123/// ```
124/// let jar = nescookie::open("tests/cookies.txt").unwrap();
125/// ```
126#[inline]
127pub fn open(path: impl AsRef<Path>) -> Result<CookieJar, Error> {
128 CookieJarBuilder::new().open(path).map(|jar| jar.finish())
129}
130/// Parses a [`CookieJar`](cookie::CookieJar) from something that implements [`BufRead`](std::io::BufRead)
131///
132/// ```
133/// use std::io::Cursor;
134///
135/// let buf = Cursor::new(b".pixiv.net TRUE / TRUE 1784339332 p_ab_id 7\n");
136/// let jar = nescookie::parse_buffer(buf).unwrap();
137/// ```
138#[inline]
139pub fn parse_buffer(buf: impl BufRead) -> Result<CookieJar, Error> {
140 CookieJarBuilder::new()
141 .parse_buffer(buf)
142 .map(|jar| jar.finish())
143}
144/// Parses a [`CookieJar`](cookie::CookieJar) from an str
145///
146/// ```
147/// let content = ".pixiv.net TRUE / TRUE 1784339332 p_ab_id 7\n";
148/// let jar = nescookie::parse(content).unwrap();
149/// ```
150#[inline]
151pub fn parse(s: &str) -> Result<CookieJar, Error> {
152 CookieJarBuilder::new().parse(s).map(|jar| jar.finish())
153}