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}