use std::borrow::Cow; use std::error::Error; use std::convert::{From, TryFrom}; use std::str::Utf8Error; use std::fmt;
#[allow(unused_imports, deprecated)] use std::ascii::AsciiExt;
#[cfg(feature = "percent-encode")] use percent_encoding::percent_decode; use time::{PrimitiveDateTime, Duration, OffsetDateTime}; use time::{parsing::Parsable, macros::format_description, format_description::FormatItem};
usecrate::{Cookie, SameSite, CookieStr};
// The three formats spec'd in http://tools.ietf.org/html/rfc2616#section-3.3.1. // Additional ones as encountered in the real world. pubstatic FMT1: &[FormatItem<'_>] = format_description!("[weekday repr:short], [day] [month repr:short] [year padding:none] [hour]:[minute]:[second] GMT"); pubstatic FMT2: &[FormatItem<'_>] = format_description!("[weekday], [day]-[month repr:short]-[year repr:last_two] [hour]:[minute]:[second] GMT"); pubstatic FMT3: &[FormatItem<'_>] = format_description!("[weekday repr:short] [month repr:short] [day padding:space] [hour]:[minute]:[second] [year padding:none]"); pubstatic FMT4: &[FormatItem<'_>] = format_description!("[weekday repr:short], [day]-[month repr:short]-[year padding:none] [hour]:[minute]:[second] GMT");
/// Enum corresponding to a parsing error. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[non_exhaustive] pubenum ParseError { /// The cookie did not contain a name/value pair.
MissingPair, /// The cookie's name was empty.
EmptyName, /// Decoding the cookie's name or value resulted in invalid UTF-8.
Utf8Error(Utf8Error),
}
impl ParseError { /// Returns a description of this error as a string pubfn as_str(&self) -> &'static str { match *self {
ParseError::MissingPair => "the cookie is missing a name/value pair",
ParseError::EmptyName => "the cookie's name is empty",
ParseError::Utf8Error(_) => { "decoding the cookie's name or value resulted in invalid UTF-8"
}
}
}
}
iflet (&Cow::Borrowed(_), &Cow::Borrowed(_)) = (&decoded_name, &decoded_value) {
Ok(None)
} else { let name = CookieStr::Concrete(Cow::Owned(decoded_name.into())); let val = CookieStr::Concrete(Cow::Owned(decoded_value.into()));
Ok(Some((name, val)))
}
}
#[cfg(not(feature = "percent-encode"))] fn name_val_decoded(
_: &str,
_: &str
) -> Result<Option<(CookieStr<'static>, CookieStr<'static>)>, ParseError> {
unreachable!("This function should never be called with 'percent-encode' disabled!")
}
match (s.chars().next(), s.chars().last()) {
(Some('"'), Some('"')) => &s[1..(s.len() - 1)],
_ => s
}
}
// This function does the real parsing but _does not_ set the `cookie_string` in // the returned cookie object. This only exists so that the borrow to `s` is // returned at the end of the call, allowing the `cookie_string` field to be // set in the outer `parse` function. fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> { letmut attributes = s.split(';');
// Determine the name = val. let key_value = attributes.next().expect("first str::split().next() returns Some"); let (name, value) = match key_value.find('=') {
Some(i) => { let (key, value) = (key_value[..i].trim(), key_value[(i + 1)..].trim());
(key, trim_quotes(value).trim())
},
None => return Err(ParseError::MissingPair)
};
if name.is_empty() { return Err(ParseError::EmptyName);
}
// If there is nothing to decode, or we're not decoding, use indexes. let indexed_names = |s, name, value| { let name_indexes = indexes_of(name, s).expect("name sub"); let value_indexes = indexes_of(value, s).expect("value sub"); let name = CookieStr::Indexed(name_indexes.0, name_indexes.1); let value = CookieStr::Indexed(value_indexes.0, value_indexes.1);
(name, value)
};
// Create a cookie with all of the defaults. We'll fill things in while we // iterate through the parameters below. let (name, value) = if decode { match name_val_decoded(name, value)? {
Some((name, value)) => (name, value),
None => indexed_names(s, name, value)
}
} else {
indexed_names(s, name, value)
};
for attr in attributes { let (key, value) = match attr.find('=') {
Some(i) => (attr[..i].trim(), Some(attr[(i + 1)..].trim())),
None => (attr.trim(), None),
};
match (&*key.to_ascii_lowercase(), value) {
("secure", _) => cookie.secure = Some(true),
("httponly", _) => cookie.http_only = Some(true),
("max-age", Some(mut v)) => cookie.max_age = { let is_negative = v.starts_with('-'); if is_negative {
v = &v[1..];
}
if !v.chars().all(|d| d.is_digit(10)) { continue
}
// From RFC 6265 5.2.2: neg values indicate that the earliest // expiration should be used, so set the max age to 0 seconds. if is_negative {
Some(Duration::ZERO)
} else {
Some(v.parse::<i64>()
.map(Duration::seconds)
.unwrap_or_else(|_| Duration::seconds(i64::max_value())))
}
},
("domain", Some(mut domain)) if !domain.is_empty() => { if domain.starts_with('.') {
domain = &domain[1..];
}
let (i, j) = indexes_of(domain, s).expect("domain sub");
cookie.domain = Some(CookieStr::Indexed(i, j));
}
("path", Some(v)) => { let (i, j) = indexes_of(v, s).expect("path sub");
cookie.path = Some(CookieStr::Indexed(i, j));
}
("samesite", Some(v)) => { if v.eq_ignore_ascii_case("strict") {
cookie.same_site = Some(SameSite::Strict);
} elseif v.eq_ignore_ascii_case("lax") {
cookie.same_site = Some(SameSite::Lax);
} elseif v.eq_ignore_ascii_case("none") {
cookie.same_site = Some(SameSite::None);
} else { // We do nothing here, for now. When/if the `SameSite` // attribute becomes standard, the spec says that we should // ignore this cookie, i.e, fail to parse it, when an // invalid value is passed in. The draft is at // http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-same-site.html.
}
}
("expires", Some(v)) => { let tm = parse_date(v, &FMT1)
.or_else(|_| parse_date(v, &FMT2))
.or_else(|_| parse_date(v, &FMT3))
.or_else(|_| parse_date(v, &FMT4)); // .or_else(|_| parse_date(v, &FMT5));
iflet Ok(time) = tm {
cookie.expires = Some(time.into())
}
}
_ => { // We're going to be permissive here. If we have no idea what // this is, then it's something nonstandard. We're not going to // store it (because it's not compliant), but we're also not // going to emit an error.
}
}
}
Ok(cookie)
}
pub(crate) fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result<Cookie<'c>, ParseError> where S: Into<Cow<'c, str>>
{ let s = cow.into(); letmut cookie = parse_inner(&s, decode)?;
cookie.cookie_string = Some(s);
Ok(cookie)
}
pub(crate) fn parse_date(s: &str, format: &impl Parsable) -> Result<OffsetDateTime, time::Error> { // Parse. Handle "abbreviated" dates like Chromium. See cookie#162. letmut date = format.parse(s.as_bytes())?; iflet Some(y) = date.year().or_else(|| date.year_last_two().map(|v| v as i32)) { let offset = match y { 0..=68 => 2000, 69..=99 => 1900,
_ => 0,
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.