Skip to main content

form_net/urlencoded/
raw.rs

1//! The allocation-free urlencoded scanner.
2
3use core::fmt;
4
5use crate::error::{EncodingError, FormError, Limit, Location};
6use crate::limits::{Limits, PercentPolicy};
7use crate::urlencoded::decode::decode_component;
8
9const HAS_EQUALS: u8 = 1;
10const NAME_DECODE: u8 = 2;
11const VALUE_DECODE: u8 = 4;
12
13/// Iterates the pairs of a urlencoded body without allocating or copying.
14///
15/// Budgets are checked per sequence before the sequence is handed out: body
16/// size (every byte, empty sequences included), field count, raw name and
17/// value lengths, and the running total of raw value lengths. After the
18/// first error the iterator returns `None`.
19///
20/// # Examples
21///
22/// ```
23/// use form_net::Limits;
24///
25/// let pairs: Vec<_> = form_net::raw_pairs(b"a=1&&b", &Limits::DEFAULT)
26///     .map(|p| p.map(|p| (p.name_raw(), p.value_raw(), p.has_equals())))
27///     .collect::<Result<_, _>>()?;
28/// assert_eq!(pairs, [(&b"a"[..], &b"1"[..], true), (&b"b"[..], &b""[..], false)]);
29/// # Ok::<(), form_net::FormError>(())
30/// ```
31#[must_use]
32pub fn raw_pairs<'a>(input: &'a [u8], limits: &Limits) -> RawPairs<'a> {
33    RawPairs {
34        input,
35        pos: 0,
36        budget: Budget::new(limits),
37        done: false,
38        checked_body: false,
39    }
40}
41
42/// The per-sequence budgets, shared by [`raw_pairs`] and the streaming
43/// parser so both refuse the same sequence with the same error.
44#[derive(Clone, Copy, Debug)]
45pub(crate) struct Budget {
46    // The effective limits this budget checks, not the whole `Limits`: the
47    // iterators that carry a budget are moved by value through adapters.
48    max_fields: u32,
49    max_name: u32,
50    max_value: u32,
51    max_text: u64,
52    pub(crate) body: u64,
53    fields: u32,
54    text: u64,
55}
56
57impl Budget {
58    pub(crate) const fn new(limits: &Limits) -> Budget {
59        Budget {
60            max_fields: limits.max_fields,
61            max_name: limits.name(),
62            max_value: limits.value(),
63            max_text: limits.text(),
64            body: limits.body(),
65            fields: 0,
66            text: 0,
67        }
68    }
69
70    /// Counts and checks one complete, non-empty sequence: field count,
71    /// name length, value length, then the text total.
72    pub(crate) fn admit(&mut self, pair: &RawPair<'_>) -> Result<(), FormError> {
73        let at = Location::at(pair.offset);
74        self.fields = self.fields.saturating_add(1);
75        if self.fields > self.max_fields {
76            return Err(FormError::limit(Limit::Fields, at));
77        }
78        if pair.name.len() as u64 > u64::from(self.max_name) {
79            return Err(FormError::limit(Limit::NameLength, at));
80        }
81        let value_at = pair.value_offset();
82        if pair.value.len() as u64 > u64::from(self.max_value) {
83            return Err(FormError::limit(Limit::ValueLength, value_at));
84        }
85        self.text = self.text.saturating_add(pair.value.len() as u64);
86        if self.text > self.max_text {
87            return Err(FormError::limit(Limit::TextTotal, value_at));
88        }
89        Ok(())
90    }
91
92    /// Checks the start of a sequence that is not complete yet: when its
93    /// complete form is certain to be refused, refuses it now with the error
94    /// [`admit`](Self::admit) would give, so a held sequence never outgrows
95    /// `urlencoded_buffer_len`.
96    pub(crate) fn admit_partial(
97        &self,
98        offset: u64,
99        name_len: usize,
100        has_equals: bool,
101        value_len: usize,
102    ) -> Result<(), FormError> {
103        let name_over = name_len as u64 > u64::from(self.max_name);
104        let value_over = has_equals && value_len as u64 > u64::from(self.max_value);
105        if !name_over && !value_over {
106            return Ok(());
107        }
108        if self.fields.saturating_add(1) > self.max_fields {
109            return Err(FormError::limit(Limit::Fields, Location::at(offset)));
110        }
111        if name_over {
112            return Err(FormError::limit(Limit::NameLength, Location::at(offset)));
113        }
114        let value_at = offset.saturating_add(name_len as u64).saturating_add(1);
115        Err(FormError::limit(Limit::ValueLength, Location::at(value_at)))
116    }
117
118    pub(crate) fn reset(&mut self) {
119        self.fields = 0;
120        self.text = 0;
121    }
122}
123
124/// Scans one sequence from `start`: returns where it ends (the next `&`, or
125/// the end), where its first `=` is, and its flags. The byte set narrows as
126/// facts are learned: once a side is known to need decoding, its `%` and
127/// `+` stop mattering, so a value full of escapes is crossed in eight-byte
128/// steps instead of stopping at every `%`.
129#[inline]
130fn scan(input: &[u8], start: usize) -> (usize, Option<usize>, u8) {
131    let mut flags = 0u8;
132    let mut i = start;
133    // The name: up to the first `=` or `&`.
134    let eq = loop {
135        i = if flags & NAME_DECODE == 0 {
136            crate::scan::find_any(input, i, *b"&=%+")
137        } else {
138            crate::scan::find_any(input, i, *b"&=")
139        };
140        match input.get(i) {
141            None | Some(b'&') => return (i, None, flags),
142            Some(b'=') => break i,
143            Some(_) => flags |= NAME_DECODE,
144        }
145        i = i.wrapping_add(1);
146    };
147    flags |= HAS_EQUALS;
148    i = eq.wrapping_add(1);
149    // The value: `=` is data now; after the first `%` or `+` only `&` ends it.
150    loop {
151        i = if flags & VALUE_DECODE == 0 {
152            crate::scan::find_any(input, i, *b"&%+")
153        } else {
154            crate::scan::find_byte(input, i, b'&')
155        };
156        match input.get(i) {
157            None | Some(b'&') => return (i, Some(eq), flags),
158            Some(_) => flags |= VALUE_DECODE,
159        }
160        i = i.wrapping_add(1);
161    }
162}
163
164/// The iterator returned by [`raw_pairs`].
165#[derive(Clone)]
166pub struct RawPairs<'a> {
167    input: &'a [u8],
168    pos: usize,
169    budget: Budget,
170    done: bool,
171    checked_body: bool,
172}
173
174impl fmt::Debug for RawPairs<'_> {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        f.debug_struct("RawPairs")
177            .field("position", &self.pos)
178            .field("fields", &self.budget.fields)
179            .finish_non_exhaustive()
180    }
181}
182
183impl<'a> RawPairs<'a> {
184    fn fail(&mut self, err: FormError) -> Option<Result<RawPair<'a>, FormError>> {
185        self.done = true;
186        Some(Err(err))
187    }
188}
189
190impl<'a> Iterator for RawPairs<'a> {
191    type Item = Result<RawPair<'a>, FormError>;
192
193    fn next(&mut self) -> Option<Self::Item> {
194        if self.done {
195            return None;
196        }
197        if !self.checked_body {
198            self.checked_body = true;
199            if self.input.len() as u64 > self.budget.body {
200                return self.fail(FormError::limit(
201                    Limit::Body,
202                    Location::at(self.budget.body),
203                ));
204            }
205        }
206        loop {
207            if self.pos >= self.input.len() {
208                self.done = true;
209                return None;
210            }
211            let start = self.pos;
212            let (end, eq, flags) = scan(self.input, start);
213            self.pos = end.saturating_add(1).min(self.input.len());
214            if end == start {
215                continue;
216            }
217            let seq = self.input.get(start..end).unwrap_or(&[]);
218            let (name, value) = match eq {
219                Some(e) => {
220                    let at = e.wrapping_sub(start);
221                    (
222                        seq.get(..at).unwrap_or(&[]),
223                        seq.get(at.wrapping_add(1)..).unwrap_or(&[]),
224                    )
225                }
226                None => (seq, &[][..]),
227            };
228            let pair = RawPair {
229                name,
230                value,
231                flags,
232                offset: start as u64,
233            };
234            if let Err(e) = self.budget.admit(&pair) {
235                return self.fail(e);
236            }
237            return Some(Ok(pair));
238        }
239    }
240}
241
242impl core::iter::FusedIterator for RawPairs<'_> {}
243
244/// One `name=value` sequence, undecoded.
245///
246/// `Debug` prints the offset and the lengths only: values can be secrets.
247#[derive(Clone, Copy)]
248pub struct RawPair<'a> {
249    name: &'a [u8],
250    value: &'a [u8],
251    flags: u8,
252    offset: u64,
253}
254
255impl fmt::Debug for RawPair<'_> {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        f.debug_struct("RawPair")
258            .field("offset", &self.offset)
259            .field("name_len", &self.name.len())
260            .field("value_len", &self.value.len())
261            .finish()
262    }
263}
264
265impl<'a> RawPair<'a> {
266    /// Splits one sequence (no `&` in it) found at `offset` in the body.
267    pub(crate) fn split(seq: &'a [u8], offset: u64) -> RawPair<'a> {
268        let (_, eq, flags) = scan(seq, 0);
269        let (name, value) = match eq {
270            Some(e) => (
271                seq.get(..e).unwrap_or(&[]),
272                seq.get(e.wrapping_add(1)..).unwrap_or(&[]),
273            ),
274            None => (seq, &[][..]),
275        };
276        RawPair {
277            name,
278            value,
279            flags,
280            offset,
281        }
282    }
283
284    /// The name as text when it needs no decoding. With `whole` (the body,
285    /// already validated as UTF-8 in one pass) it is cut from it: the cut
286    /// falls at ASCII `&` or `=`, so it is an O(1) boundary check instead of
287    /// a UTF-8 pass per name (D-104).
288    #[cfg(feature = "alloc")]
289    #[inline]
290    pub(crate) fn name_text(&self, whole: Option<&'a str>) -> Option<&'a str> {
291        if self.name_needs_decoding() {
292            return None;
293        }
294        let start = usize::try_from(self.offset).ok()?;
295        match whole {
296            Some(w) => w.get(start..start.checked_add(self.name.len())?),
297            None => core::str::from_utf8(self.name).ok(),
298        }
299    }
300
301    /// As [`name_text`](Self::name_text), for the value.
302    #[cfg(feature = "alloc")]
303    #[inline]
304    pub(crate) fn value_text(&self, whole: Option<&'a str>) -> Option<&'a str> {
305        if self.value_needs_decoding() {
306            return None;
307        }
308        if self.value.is_empty() {
309            return Some("");
310        }
311        let start = usize::try_from(self.offset)
312            .ok()?
313            .checked_add(self.name.len())?
314            .checked_add(1)?;
315        match whole {
316            Some(w) => w.get(start..start.checked_add(self.value.len())?),
317            None => core::str::from_utf8(self.value).ok(),
318        }
319    }
320
321    fn value_offset(&self) -> Location {
322        Location::at(
323            self.offset
324                .saturating_add(self.name.len() as u64)
325                .saturating_add(1),
326        )
327    }
328
329    /// The name as sent, before decoding.
330    ///
331    /// # Examples
332    ///
333    /// ```
334    /// let p = form_net::raw_pairs(b"a+b=1", &form_net::Limits::DEFAULT).next().unwrap()?;
335    /// assert_eq!(p.name_raw(), b"a+b");
336    /// # Ok::<(), form_net::FormError>(())
337    /// ```
338    #[must_use]
339    pub const fn name_raw(&self) -> &'a [u8] {
340        self.name
341    }
342
343    /// The value as sent, before decoding.
344    ///
345    /// # Examples
346    ///
347    /// ```
348    /// let p = form_net::raw_pairs(b"a=%41", &form_net::Limits::DEFAULT).next().unwrap()?;
349    /// assert_eq!(p.value_raw(), b"%41");
350    /// # Ok::<(), form_net::FormError>(())
351    /// ```
352    #[must_use]
353    pub const fn value_raw(&self) -> &'a [u8] {
354        self.value
355    }
356
357    /// Whether the sequence contained `=`: `a` and `a=` both have an empty
358    /// value, but only `a=` has an equals sign.
359    ///
360    /// # Examples
361    ///
362    /// ```
363    /// use form_net::Limits;
364    ///
365    /// let mut it = form_net::raw_pairs(b"a&b=", &Limits::DEFAULT);
366    /// assert!(!it.next().unwrap()?.has_equals());
367    /// assert!(it.next().unwrap()?.has_equals());
368    /// # Ok::<(), form_net::FormError>(())
369    /// ```
370    #[must_use]
371    pub const fn has_equals(&self) -> bool {
372        self.flags & HAS_EQUALS != 0
373    }
374
375    /// Whether the name contains `%` or `+`.
376    ///
377    /// # Examples
378    ///
379    /// ```
380    /// let p = form_net::raw_pairs(b"a+b=1", &form_net::Limits::DEFAULT).next().unwrap()?;
381    /// assert!(p.name_needs_decoding() && !p.value_needs_decoding());
382    /// # Ok::<(), form_net::FormError>(())
383    /// ```
384    #[must_use]
385    pub const fn name_needs_decoding(&self) -> bool {
386        self.flags & NAME_DECODE != 0
387    }
388
389    /// Whether the value contains `%` or `+`.
390    ///
391    /// # Examples
392    ///
393    /// ```
394    /// let p = form_net::raw_pairs(b"a=1%20", &form_net::Limits::DEFAULT).next().unwrap()?;
395    /// assert!(p.value_needs_decoding());
396    /// # Ok::<(), form_net::FormError>(())
397    /// ```
398    #[must_use]
399    pub const fn value_needs_decoding(&self) -> bool {
400        self.flags & VALUE_DECODE != 0
401    }
402
403    /// The name with no copy, when it needs no decoding and is valid UTF-8.
404    ///
405    /// # Examples
406    ///
407    /// ```
408    /// let p = form_net::raw_pairs(b"email=x", &form_net::Limits::DEFAULT).next().unwrap()?;
409    /// assert_eq!(p.name_str(), Some("email"));
410    /// # Ok::<(), form_net::FormError>(())
411    /// ```
412    #[must_use]
413    pub fn name_str(&self) -> Option<&'a str> {
414        if self.name_needs_decoding() {
415            None
416        } else {
417            core::str::from_utf8(self.name).ok()
418        }
419    }
420
421    /// The value with no copy, when it needs no decoding and is valid UTF-8.
422    ///
423    /// # Examples
424    ///
425    /// ```
426    /// let p = form_net::raw_pairs(b"q=a+b", &form_net::Limits::DEFAULT).next().unwrap()?;
427    /// assert_eq!(p.value_str(), None);
428    /// # Ok::<(), form_net::FormError>(())
429    /// ```
430    #[must_use]
431    pub fn value_str(&self) -> Option<&'a str> {
432        if self.value_needs_decoding() {
433            None
434        } else {
435            core::str::from_utf8(self.value).ok()
436        }
437    }
438
439    /// Decodes the name into `out` (see [`decode_into`](crate::decode_into)).
440    ///
441    /// # Errors
442    ///
443    /// `Encoding { InvalidPercent | InvalidUtf8 }` located in the body;
444    /// `BufferTooSmall` when `out` is shorter than the raw name.
445    ///
446    /// # Examples
447    ///
448    /// ```
449    /// use form_net::PercentPolicy;
450    ///
451    /// let p = form_net::raw_pairs(b"a%20b=1", &form_net::Limits::DEFAULT).next().unwrap()?;
452    /// let mut out = [0u8; 16];
453    /// assert_eq!(p.decode_name(&mut out, PercentPolicy::Refuse)?, "a b");
454    /// # Ok::<(), form_net::FormError>(())
455    /// ```
456    pub fn decode_name<'b>(
457        &self,
458        out: &'b mut [u8],
459        policy: PercentPolicy,
460    ) -> Result<&'b str, FormError>
461    where
462        'a: 'b,
463    {
464        decoded(self.name, self.name_needs_decoding(), out, policy)
465            .map_err(|e| e.shifted(self.offset))
466    }
467
468    /// Decodes the value into `out` (see [`decode_into`](crate::decode_into)).
469    ///
470    /// # Errors
471    ///
472    /// As [`decode_name`](Self::decode_name).
473    ///
474    /// # Examples
475    ///
476    /// ```
477    /// use form_net::PercentPolicy;
478    ///
479    /// let p = form_net::raw_pairs(b"q=rust+forms", &form_net::Limits::DEFAULT).next().unwrap()?;
480    /// let mut out = [0u8; 16];
481    /// assert_eq!(p.decode_value(&mut out, PercentPolicy::Refuse)?, "rust forms");
482    /// # Ok::<(), form_net::FormError>(())
483    /// ```
484    pub fn decode_value<'b>(
485        &self,
486        out: &'b mut [u8],
487        policy: PercentPolicy,
488    ) -> Result<&'b str, FormError>
489    where
490        'a: 'b,
491    {
492        let at = self
493            .offset
494            .saturating_add(self.name.len() as u64)
495            .saturating_add(1);
496        decoded(self.value, self.value_needs_decoding(), out, policy).map_err(|e| e.shifted(at))
497    }
498
499    /// The byte offset of the sequence in the body.
500    ///
501    /// # Examples
502    ///
503    /// ```
504    /// let mut it = form_net::raw_pairs(b"a=1&bb=2", &form_net::Limits::DEFAULT);
505    /// let _ = it.next();
506    /// assert_eq!(it.next().unwrap()?.offset(), 4);
507    /// # Ok::<(), form_net::FormError>(())
508    /// ```
509    #[must_use]
510    pub const fn offset(&self) -> u64 {
511        self.offset
512    }
513}
514
515/// A component as text: borrowed from the body when it holds no `%` or `+`
516/// (no copy), decoded into `out` otherwise. `out` must be as long as the
517/// raw component either way, so whether a call fails never depends on the
518/// bytes.
519fn decoded<'b>(
520    raw: &'b [u8],
521    needs_decoding: bool,
522    out: &'b mut [u8],
523    policy: PercentPolicy,
524) -> Result<&'b str, FormError> {
525    if needs_decoding {
526        return decode_component(raw, out, policy);
527    }
528    if out.len() < raw.len() {
529        return Err(FormError::BufferTooSmall { needed: raw.len() });
530    }
531    // Located at the component's start, as decoding locates it.
532    core::str::from_utf8(raw)
533        .map_err(|_| FormError::encoding(EncodingError::InvalidUtf8, Location::at(0)))
534}
535
536#[cfg(test)]
537mod tests {
538    use super::*;
539    use alloc::vec::Vec;
540
541    type Pairs<'a> = Vec<(&'a [u8], &'a [u8])>;
542
543    /// 0.9: a plain component is borrowed from the body, not copied; the
544    /// buffer rule and the errors are the same either way.
545    #[test]
546    fn test_decode_borrows_plain_components_and_keeps_the_rules() {
547        let body = b"name=value&a%20b=c+d&bad=\xff";
548        let mut it = super::raw_pairs(body, &crate::Limits::DEFAULT);
549        let mut out = [0u8; 16];
550        let p = it.next().unwrap().unwrap();
551        let name = p.decode_name(&mut out, PercentPolicy::Refuse).unwrap();
552        assert_eq!(name, "name");
553        assert_eq!(name.as_ptr(), body.as_ptr(), "borrowed from the body");
554        let value = p.decode_value(&mut out, PercentPolicy::Refuse).unwrap();
555        assert_eq!(value.as_ptr(), body[5..].as_ptr());
556        let mut short = [0u8; 3];
557        assert!(matches!(
558            p.decode_name(&mut short, PercentPolicy::Refuse),
559            Err(FormError::BufferTooSmall { needed: 4 })
560        ));
561        let p = it.next().unwrap().unwrap();
562        assert_eq!(
563            p.decode_name(&mut out, PercentPolicy::Refuse).unwrap(),
564            "a b"
565        );
566        assert_eq!(
567            p.decode_value(&mut out, PercentPolicy::Refuse).unwrap(),
568            "c d"
569        );
570        let p = it.next().unwrap().unwrap();
571        let e = p.decode_value(&mut out, PercentPolicy::Refuse).unwrap_err();
572        assert_eq!(
573            (e.code(), e.location().map(|l| l.offset)),
574            ("encoding.invalid_utf8", Some(25))
575        );
576        // The same error as decoding gives for an escaped bad byte there.
577        let mut it = super::raw_pairs(b"name=value&a%20b=c+d&bad=%FF", &crate::Limits::DEFAULT);
578        let p = it.nth(2).unwrap().unwrap();
579        let d = p.decode_value(&mut out, PercentPolicy::Refuse).unwrap_err();
580        assert_eq!((d.code(), d.location()), (e.code(), e.location()));
581    }
582
583    fn pairs(input: &[u8]) -> Result<Pairs<'_>, FormError> {
584        raw_pairs(input, &Limits::DEFAULT)
585            .map(|p| p.map(|p| (p.name_raw(), p.value_raw())))
586            .collect()
587    }
588
589    #[test]
590    fn test_raw_pairs_whatwg_splitting() {
591        assert_eq!(pairs(b"").unwrap(), []);
592        assert_eq!(pairs(b"&&&").unwrap(), []);
593        assert_eq!(pairs(b"=").unwrap(), [(&b""[..], &b""[..])]);
594        assert_eq!(pairs(b"a==b").unwrap(), [(&b"a"[..], &b"=b"[..])]);
595        assert_eq!(pairs(b"=a").unwrap(), [(&b""[..], &b"a"[..])]);
596        assert_eq!(pairs(b"a;b=c").unwrap(), [(&b"a;b"[..], &b"c"[..])]);
597        assert_eq!(
598            pairs(b"a=1&b=2&").unwrap(),
599            [(&b"a"[..], &b"1"[..]), (&b"b"[..], &b"2"[..])]
600        );
601    }
602
603    #[test]
604    fn test_raw_pairs_flags_per_side() {
605        let p = raw_pairs(b"a%=b", &Limits::DEFAULT)
606            .next()
607            .unwrap()
608            .unwrap();
609        assert!(p.name_needs_decoding() && !p.value_needs_decoding());
610        let p = raw_pairs(b"a=b+%", &Limits::DEFAULT)
611            .next()
612            .unwrap()
613            .unwrap();
614        assert!(!p.name_needs_decoding() && p.value_needs_decoding());
615        let p = raw_pairs(b"a=b=+", &Limits::DEFAULT)
616            .next()
617            .unwrap()
618            .unwrap();
619        assert!(p.value_needs_decoding() && p.has_equals());
620    }
621
622    #[test]
623    fn test_raw_pairs_field_limit_n_and_n_plus_one() {
624        let l = Limits::DEFAULT.with_max_fields(2);
625        assert_eq!(raw_pairs(b"a&b", &l).count(), 2);
626        let r: Result<Vec<_>, _> = raw_pairs(b"a&b&c", &l).collect();
627        assert!(matches!(
628            r,
629            Err(FormError::Limit {
630                limit: Limit::Fields,
631                at
632            }) if at.offset == 4
633        ));
634    }
635
636    #[test]
637    fn test_raw_pairs_length_limits_at_the_edge() {
638        let l = Limits::DEFAULT
639            .with_max_name_bytes(3)
640            .with_max_value_bytes(3);
641        assert!(raw_pairs(b"abc=def", &l).all(|p| p.is_ok()));
642        let e = raw_pairs(b"abcd=1", &l).next().unwrap().unwrap_err();
643        assert!(matches!(
644            e,
645            FormError::Limit {
646                limit: Limit::NameLength,
647                ..
648            }
649        ));
650        let e = raw_pairs(b"a=defg", &l).next().unwrap().unwrap_err();
651        assert!(matches!(e, FormError::Limit { limit: Limit::ValueLength, at } if at.offset == 2));
652        let l = Limits::DEFAULT.with_max_text_bytes(4);
653        let r: Result<Vec<_>, _> = raw_pairs(b"a=12&b=345", &l).collect();
654        assert!(matches!(
655            r,
656            Err(FormError::Limit {
657                limit: Limit::TextTotal,
658                ..
659            })
660        ));
661    }
662
663    #[test]
664    fn test_raw_pairs_body_limit_counts_empty_sequences() {
665        let l = Limits::DEFAULT.with_max_body_bytes(4);
666        assert_eq!(raw_pairs(b"&&&&", &l).count(), 0);
667        let e = raw_pairs(b"&&&&&", &l).next().unwrap().unwrap_err();
668        assert!(matches!(
669            e,
670            FormError::Limit {
671                limit: Limit::Body,
672                ..
673            }
674        ));
675    }
676
677    #[test]
678    fn test_raw_pairs_stops_after_error() {
679        let l = Limits::DEFAULT.with_max_fields(0);
680        let mut it = raw_pairs(b"a&b", &l);
681        assert!(it.next().unwrap().is_err());
682        assert!(it.next().is_none());
683    }
684
685    #[test]
686    fn test_raw_pair_debug_hides_values() {
687        let p = raw_pairs(b"password=hunter2", &Limits::DEFAULT)
688            .next()
689            .unwrap()
690            .unwrap();
691        let s = alloc::format!("{p:?}");
692        assert!(!s.contains("hunter2") && !s.contains("password"));
693    }
694}