use http::header::{self, HeaderMap, HeaderValue};
pub fn weak_eq(mut a: &[u8], mut b: &[u8]) -> bool {
if a.starts_with(b"W/") {
a = &a[2..];
}
if b.starts_with(b"W/") {
b = &b[2..];
}
a == b
}
pub fn strong_eq(a: &[u8], b: &[u8]) -> bool {
a == b && !a.starts_with(b"W/")
}
struct List<'a> {
remaining: &'a [u8],
corrupt: bool,
}
impl<'a> List<'a> {
fn from(l: &[u8]) -> List {
List {
remaining: l,
corrupt: false,
}
}
}
impl<'a> Iterator for List<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.remaining.is_empty() {
return None;
}
let end = if self.remaining.starts_with(b"W/\"") {
self.remaining[3..]
.iter()
.position(|&b| b == b'"')
.map(|p| p + 3)
} else if self.remaining.starts_with(b"\"") {
self.remaining[1..]
.iter()
.position(|&b| b == b'"')
.map(|p| p + 1)
} else {
self.corrupt = true;
None
};
let end = match end {
None => {
self.corrupt = true;
return None;
}
Some(e) => e,
};
let (etag, mut rem) = self.remaining.split_at(end + 1);
if rem.starts_with(b",") {
rem = &rem[1..];
while !rem.is_empty() && (rem[0] == b' ' || rem[0] == b'\t') {
rem = &rem[1..];
}
}
self.remaining = rem;
Some(etag)
}
}
pub fn none_match(etag: &Option<HeaderValue>, req_hdrs: &HeaderMap) -> Option<bool> {
let m = match req_hdrs.get(header::IF_NONE_MATCH) {
None => return None,
Some(m) => m.as_bytes(),
};
if m == b"*" {
return Some(false);
}
let mut none_match = true;
if let Some(ref some_etag) = *etag {
let mut items = List::from(m);
for item in &mut items {
if none_match && weak_eq(item, some_etag.as_bytes()) {
none_match = false;
}
}
if items.corrupt {
return None; }
}
Some(none_match)
}
pub fn any_match(etag: &Option<HeaderValue>, req_hdrs: &HeaderMap) -> Result<bool, &'static str> {
let m = match req_hdrs.get(header::IF_MATCH) {
None => return Ok(true),
Some(m) => m.as_bytes(),
};
if m == b"*" {
return Ok(true);
}
let mut any_match = false;
if let Some(ref some_etag) = *etag {
let mut items = List::from(m);
for item in &mut items {
if !any_match && strong_eq(item, some_etag.as_bytes()) {
any_match = true;
}
}
if items.corrupt {
return Err("Unparseable If-Match header");
}
}
Ok(any_match)
}
#[cfg(test)]
mod tests {
use super::List;
#[test]
fn weak_eq() {
assert!(super::weak_eq(b"\"foo\"", b"\"foo\""));
assert!(!super::weak_eq(b"\"foo\"", b"\"bar\""));
assert!(super::weak_eq(b"W/\"foo\"", b"\"foo\""));
assert!(super::weak_eq(b"\"foo\"", b"W/\"foo\""));
assert!(super::weak_eq(b"W/\"foo\"", b"W/\"foo\""));
assert!(!super::weak_eq(b"W/\"foo\"", b"W/\"bar\""));
}
#[test]
fn strong_eq() {
assert!(super::strong_eq(b"\"foo\"", b"\"foo\""));
assert!(!super::strong_eq(b"\"foo\"", b"\"bar\""));
assert!(!super::strong_eq(b"W/\"foo\"", b"\"foo\""));
assert!(!super::strong_eq(b"\"foo\"", b"W/\"foo\""));
assert!(!super::strong_eq(b"W/\"foo\"", b"W/\"foo\""));
assert!(!super::strong_eq(b"W/\"foo\"", b"W/\"bar\""));
}
#[test]
fn empty_list() {
let mut l = List::from(b"");
assert_eq!(l.next(), None);
assert!(!l.corrupt);
}
#[test]
fn nonempty_list() {
let mut l = List::from(b"\"foo\", \tW/\"bar\",W/\"baz\"");
assert_eq!(l.next(), Some(&b"\"foo\""[..]));
assert_eq!(l.next(), Some(&b"W/\"bar\""[..]));
assert_eq!(l.next(), Some(&b"W/\"baz\""[..]));
assert_eq!(l.next(), None);
assert!(!l.corrupt);
}
#[test]
fn comma_in_etag() {
let mut l = List::from(b"\"foo, bar\", \"baz\"");
assert_eq!(l.next(), Some(&b"\"foo, bar\""[..]));
assert_eq!(l.next(), Some(&b"\"baz\""[..]));
assert_eq!(l.next(), None);
assert!(!l.corrupt);
}
#[test]
fn corrupt_list() {
let mut l = List::from(b"\"foo\", bar");
assert_eq!(l.next(), Some(&b"\"foo\""[..]));
assert_eq!(l.next(), None);
assert!(l.corrupt);
}
}