pub struct TokenCheck<V> { /* private fields */ }Expand description
Verifies the anti-forgery token the moment its field ends, before any later part starts: a forged upload is refused before its first file opens a sink, so it costs no disk.
verify is called once, with the value of the text field named name
(byte for byte, as TokenField matches it); it
does the comparison (csrf-sec’s Csrf::verify, constant time) and
returns Err with the refusal to report. The check refuses, with
status 403:
csrf.missing: a file part starts before a token was verified, or the body ends without one;csrf.duplicate: a second field with the token’s name (two tokens would let an attacker choose which one is verified);csrf.not_text: a token that is not UTF-8;- whatever
verifyreturns.
Another inspector in the pair must not skip the token’s part: the check
never sees a value that was not read, so it refuses the body with
csrf.token_skipped and status 500, a configuration error to fix in the
other inspector, not a client’s.
It works for multipart and urlencoded bodies alike, and composes with
other inspectors as a pair: (customs, check). Browsers send fields in
document order, so the form puts the hidden token input before its file
inputs. With TokenField::anywhere, where files may come first, verify
captured_token (or the
upload’s) after the parse instead. The token field stays in the result; leave it out of a typed
form with ignore.
Cost: one name comparison per text field, and verify once.
§Examples
use form_net::{Limits, Policy, Refusal, TokenCheck};
let mut check = TokenCheck::new("csrf_token", |token| {
if csrf.verify(session, token) { Ok(()) } else { Err(Refusal::new(403, "csrf.rejected")) }
});
let body = b"csrf_token=good&email=a%40b.example";
let form = form_net::parse_urlencoded_inspected(body, &Limits::DEFAULT, &Policy::DEFAULT, &mut check)?;
assert!(check.verified());
assert_eq!(form.get("email"), Some("a@b.example"));
let mut check = TokenCheck::new("csrf_token", |_| Err(Refusal::new(403, "csrf.rejected")));
let err = form_net::parse_urlencoded_inspected(b"csrf_token=bad", &Limits::DEFAULT, &Policy::DEFAULT, &mut check)
.unwrap_err();
assert_eq!((err.status_hint(), err.code()), (403, "csrf.rejected"));Implementations§
Trait Implementations§
Source§impl<V> Debug for TokenCheck<V>
impl<V> Debug for TokenCheck<V>
Source§impl<V: FnMut(&str) -> Result<(), Refusal>> Inspector for TokenCheck<V>
impl<V: FnMut(&str) -> Result<(), Refusal>> Inspector for TokenCheck<V>
Source§fn part_start(&mut self, head: &PartHead<'_>) -> Verdict
fn part_start(&mut self, head: &PartHead<'_>) -> Verdict
Source§fn field(&mut self, name: &str, value: &[u8]) -> Verdict
fn field(&mut self, name: &str, value: &[u8]) -> Verdict
Skip leaves the field out of the result.Source§fn part_end(&mut self, _summary: &PartSummary) -> Verdict
fn part_end(&mut self, _summary: &PartSummary) -> Verdict
Skip here is treated as Continue.Source§fn sniff(&mut self, head: &PartHead<'_>, first: &[u8]) -> Verdict
fn sniff(&mut self, head: &PartHead<'_>, first: &[u8]) -> Verdict
min(sniff_bytes, part length) bytes of a file part, as one
contiguous slice however the input was split, before any data call
for that part.