Skip to main content

capybara_core/protocol/http/frame/
header.rs

1use std::borrow::Cow;
2use std::fmt::{Debug, Formatter, Write};
3
4use bytes::{BufMut, Bytes, BytesMut};
5use small_map::SmallMap;
6use smallvec::SmallVec;
7
8use crate::error::CapybaraError::{ExceedMaxHttpHeaderSize, MalformedHttpPacket};
9use crate::protocol::http::httpfield::HttpField;
10use crate::protocol::http::misc;
11use crate::Result;
12
13/// store the position of header in the whole headers bytes.
14///
15/// For example: `Content-Type: application/json\r\n`
16///              |            |                    |
17///              +---> begin  +---> colon          +---> end
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub(crate) struct Position {
20    begin_: u16,
21    end_: u16,
22    colon: u16,
23    v_begin: u16,
24    v_end: u16,
25}
26
27impl Position {
28    #[inline(always)]
29    fn new(begin: usize, end: usize, colon: usize, v_begin: usize, v_end: usize) -> Position {
30        Position {
31            begin_: begin as u16,
32            end_: end as u16,
33            colon: colon as u16,
34            v_begin: v_begin as u16,
35            v_end: v_end as u16,
36        }
37    }
38
39    #[inline(always)]
40    fn begin(&self) -> usize {
41        self.begin_ as usize
42    }
43
44    #[inline(always)]
45    fn end(&self) -> usize {
46        self.end_ as usize
47    }
48
49    #[inline(always)]
50    fn breakpoint(&self) -> usize {
51        self.colon as usize
52    }
53
54    #[inline(always)]
55    fn value_begin(&self) -> usize {
56        self.v_begin as usize
57    }
58
59    #[inline(always)]
60    fn value_end(&self) -> usize {
61        self.v_end as usize
62    }
63
64    fn len(&self) -> usize {
65        (self.end_ - self.begin_) as usize
66    }
67}
68
69const N: usize = 32;
70
71#[derive(Default)]
72pub(crate) struct Indices(SmallMap<N, u16, SmallVec<[u16; 1]>, ahash::RandomState>);
73
74impl Indices {
75    #[inline(always)]
76    fn new() -> Self {
77        Self(SmallMap::new())
78    }
79
80    fn set(&mut self, key: u16, value: u16) {
81        match self.0.get_mut(&key) {
82            None => {
83                let mut v = SmallVec::<[u16; 1]>::new();
84                v.push(value);
85                self.0.insert(key, v);
86            }
87            Some(v) => v.push(value),
88        }
89    }
90
91    fn get(&self, key: &u16) -> Option<&SmallVec<[u16; 1]>> {
92        self.0.get(key)
93    }
94}
95
96pub(crate) type Positions = SmallVec<[Position; N]>;
97
98/// headers represents multiple http headers.
99pub struct Headers {
100    /// raw bytes
101    pub(crate) b: Bytes,
102    /// position anchors
103    pub(crate) pos: Positions,
104    pub(crate) indices: Indices,
105}
106
107impl Clone for Headers {
108    fn clone(&self) -> Self {
109        let mut indices = Indices::default();
110        for (i, pos) in self.pos.iter().enumerate() {
111            let key = &self.b[pos.begin()..pos.breakpoint()];
112            indices.set(misc::hash16(key), i as u16);
113        }
114        Self {
115            b: Clone::clone(&self.b),
116            pos: Clone::clone(&self.pos),
117            indices,
118        }
119    }
120
121    fn clone_from(&mut self, source: &Self) {
122        self.b = Clone::clone(&source.b);
123        Clone::clone_from(&mut self.pos, &source.pos);
124
125        let mut indices = Indices::default();
126        for (i, pos) in source.pos.iter().enumerate() {
127            let key = &source.b[pos.begin()..pos.breakpoint()];
128            indices.set(misc::hash16(key), i as u16);
129        }
130        self.indices = indices;
131    }
132}
133
134impl Debug for Headers {
135    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
136        f.debug_struct("Headers").field("b", &self.b).finish()
137    }
138}
139
140impl Headers {
141    pub fn builder() -> HeadersBuilder {
142        Default::default()
143    }
144
145    #[inline]
146    pub fn read(buf: &mut BytesMut, max_size: usize) -> Result<Option<Self>> {
147        Self::read_ext(buf, max_size, false)
148    }
149
150    #[inline]
151    pub fn read_ext(buf: &mut BytesMut, max_size: usize, lowercase: bool) -> Result<Option<Self>> {
152        let mut offset = 0;
153        let mut is_prev_r = false;
154        let mut n = 0;
155        let mut positions = Positions::new();
156        let mut eof = false;
157        let mut colon_offset = -1isize;
158        let mut indices = Indices::new();
159
160        let mut hash = 0u16;
161
162        let mut blanks = 0usize;
163        let mut blanks0 = None;
164
165        for (i, b) in buf.iter_mut().enumerate() {
166            if i >= max_size {
167                return Err(ExceedMaxHttpHeaderSize(max_size));
168            }
169
170            if colon_offset == -1 {
171                match *b {
172                    b':' => {
173                        colon_offset = i as isize;
174                        is_prev_r = false;
175                    }
176                    b'\r' => is_prev_r = true,
177                    b'\n' => {
178                        if is_prev_r {
179                            is_prev_r = false;
180                            n = i + 1;
181
182                            // bingo
183                            if i - offset == 1 {
184                                eof = true;
185                                break;
186                            }
187                            return Err(MalformedHttpPacket("incomplete CRLF".into()));
188                        }
189                    }
190                    other => {
191                        // https://datatracker.ietf.org/doc/html/rfc822#section-3.1.2
192                        if other <= 0x20 || other >= 0x7f {
193                            return Err(MalformedHttpPacket(
194                                format!("invalid header field character '{}'", other).into(),
195                            ));
196                        }
197                        is_prev_r = false;
198
199                        // TODO: should we convert '-' to '_'?
200                        hash = (hash << 5).wrapping_sub(hash);
201
202                        if other.is_ascii_uppercase() {
203                            // convert header name to lower-case
204                            if lowercase {
205                                *b = other | 0x20;
206                            }
207                            hash = hash.wrapping_add((other | 0x20) as u16);
208                        } else {
209                            hash = hash.wrapping_add(other as u16);
210                        }
211                    }
212                }
213            } else {
214                match *b {
215                    b'\r' => {
216                        blanks += 1;
217                        is_prev_r = true;
218                    }
219                    b'\n' => {
220                        blanks += 1;
221                        if is_prev_r {
222                            is_prev_r = false;
223                            n = i + 1;
224
225                            // bingo
226                            if i - offset == 1 {
227                                eof = true;
228                                break;
229                            }
230
231                            indices.set(hash, positions.len() as u16);
232                            positions.push(Position::new(
233                                offset,
234                                i + 1,
235                                colon_offset as usize,
236                                colon_offset as usize + 1 + blanks0.unwrap_or_default(),
237                                i + 1 - blanks,
238                            ));
239
240                            offset = i + 1;
241
242                            // cleanup
243                            colon_offset = -1;
244                            hash = 0;
245                            blanks = 0;
246                            blanks0 = None;
247                        }
248                    }
249                    b' ' | b'\t' | 0x0b | 0x0c | 0x85 | 0xA0 => {
250                        is_prev_r = false;
251                        blanks += 1;
252                    }
253                    _ => {
254                        if blanks0.is_none() {
255                            blanks0.replace(blanks);
256                        }
257                        blanks = 0;
258
259                        is_prev_r = false;
260                    }
261                }
262            }
263        }
264
265        Ok(if eof {
266            Some(Headers {
267                b: buf.split_to(n).freeze(),
268                pos: positions,
269                indices,
270            })
271        } else {
272            None
273        })
274    }
275
276    #[inline]
277    pub fn len(&self) -> usize {
278        self.pos.len()
279    }
280
281    #[inline]
282    pub fn is_empty(&self) -> bool {
283        self.pos.is_empty()
284    }
285
286    #[inline]
287    pub fn get_content_length(&self) -> Result<Option<usize>> {
288        match self.get_by_field(HttpField::ContentLength) {
289            Some(b) => {
290                let s = unsafe { std::str::from_utf8_unchecked(b) };
291                match s.parse::<usize>() {
292                    Ok(n) => Ok(Some(n)),
293                    Err(e) => Err(MalformedHttpPacket(
294                        format!("invalid content-length '{}'", s).into(),
295                    )),
296                }
297            }
298            None => Ok(None),
299        }
300    }
301
302    #[inline]
303    pub fn nth(&self, i: usize) -> Option<(&[u8], &[u8])> {
304        if let Some(pos) = self.pos.get(i) {
305            let key = &self.b[pos.begin()..pos.breakpoint()];
306
307            let val = {
308                let mut i = pos.breakpoint() + 1;
309                let mut j = pos.end() - 2;
310
311                while misc::is_ascii_space(self.b[j - 1]) && i < j {
312                    j -= 1;
313                }
314
315                while misc::is_ascii_space(self.b[i]) && i < j {
316                    i += 1;
317                }
318
319                &self.b[i..j]
320            };
321
322            return Some((key, val));
323        }
324
325        None
326    }
327
328    pub fn iter(&self) -> Iter {
329        Iter {
330            headers: self,
331            cursor: 0,
332        }
333    }
334
335    #[inline]
336    pub fn get_bytes(&self, key: &str) -> Option<&[u8]> {
337        if key.is_empty() {
338            return None;
339        }
340
341        let hash = misc::hash16(key.as_bytes());
342
343        if let Some(it) = self.indices.get(&hash) {
344            for i in it.iter().rev() {
345                if let Some(pos) = self.pos.get(*i as usize) {
346                    if key
347                        .as_bytes()
348                        .eq_ignore_ascii_case(&self.b[pos.begin()..pos.breakpoint()])
349                    {
350                        return Some(&self.b[pos.value_begin()..pos.value_end()]);
351                    }
352                }
353            }
354        }
355
356        None
357    }
358
359    #[inline]
360    pub fn get_by_field(&self, h: HttpField) -> Option<&[u8]> {
361        let magic: u16 = h.into();
362        let key = h.as_bytes();
363        if let Some(it) = self.indices.get(&magic) {
364            for i in it.iter().rev() {
365                if let Some(pos) = self.pos.get(*i as usize) {
366                    if key.eq_ignore_ascii_case(&self.b[pos.begin()..pos.breakpoint()]) {
367                        return Some(&self.b[pos.value_begin()..pos.value_end()]);
368                    }
369                }
370            }
371        }
372
373        None
374    }
375
376    #[inline]
377    pub fn get(&self, key: &str) -> Option<Cow<str>> {
378        if key.is_empty() {
379            return None;
380        }
381
382        let magic = misc::hash16(key.as_bytes());
383
384        if let Some(it) = self.indices.get(&magic) {
385            for i in it.iter().rev() {
386                if let Some(pos) = self.pos.get(*i as usize) {
387                    if key
388                        .as_bytes()
389                        .eq_ignore_ascii_case(&self.b[pos.begin()..pos.breakpoint()])
390                    {
391                        return Some(String::from_utf8_lossy(
392                            &self.b[pos.value_begin()..pos.value_end()],
393                        ));
394                    }
395                }
396            }
397        }
398
399        None
400    }
401
402    #[inline]
403    pub fn position(&self, key: &str) -> Option<usize> {
404        if key.is_empty() {
405            return None;
406        }
407
408        let magic = misc::hash16(key.as_bytes());
409
410        if let Some(v) = self.indices.get(&magic) {
411            for i in v.iter() {
412                let j = *i as usize;
413                if let Some(pos) = self.pos.get(j) {
414                    if key
415                        .as_bytes()
416                        .eq_ignore_ascii_case(&self.b[pos.begin()..pos.breakpoint()])
417                    {
418                        return Some(j);
419                    }
420                }
421            }
422        }
423
424        None
425    }
426
427    /// search positions from key ignore case.
428    #[inline]
429    pub fn positions(&self, key: &str) -> SmallVec<[usize; 1]> {
430        let mut ret = SmallVec::<[usize; 1]>::new();
431        if key.is_empty() {
432            return ret;
433        }
434
435        let h = misc::hash16(key.as_bytes());
436
437        if let Some(indices) = self.indices.get(&h) {
438            for i in indices {
439                let j = *i as usize;
440                if let Some(pos) = self.pos.get(j) {
441                    if key
442                        .as_bytes()
443                        .eq_ignore_ascii_case(&self.b[pos.begin()..pos.breakpoint()])
444                    {
445                        ret.push(j);
446                    }
447                }
448            }
449        }
450
451        ret
452    }
453}
454
455impl Into<Bytes> for Headers {
456    fn into(self) -> Bytes {
457        self.b
458    }
459}
460
461impl AsRef<[u8]> for Headers {
462    fn as_ref(&self) -> &[u8] {
463        self.b.as_ref()
464    }
465}
466
467#[derive(Default)]
468pub struct HeadersBuilder {
469    b: Option<BytesMut>,
470    pos: Positions,
471    indices: Indices,
472}
473
474impl HeadersBuilder {
475    pub fn len(&self) -> usize {
476        self.pos.len()
477    }
478
479    pub fn is_empty(&self) -> bool {
480        self.pos.is_empty()
481    }
482
483    pub fn put<K, V>(mut self, key: K, value: V) -> Self
484    where
485        K: AsRef<str>,
486        V: AsRef<str>,
487    {
488        let key = key.as_ref().trim();
489        let value = value.as_ref().trim();
490        let kh = misc::hash16(key.as_ref());
491
492        let (offset, size) = match &mut self.b {
493            Some(b) => {
494                let offset = b.len();
495                b.write_str(key).ok();
496                b.write_str(": ").ok();
497                b.write_str(value).ok();
498                b.write_str(unsafe { std::str::from_utf8_unchecked(misc::CRLF) })
499                    .ok();
500                (offset, b.len())
501            }
502            None => {
503                let mut b = BytesMut::with_capacity(128);
504                b.write_str(key).ok();
505                b.write_str(": ").ok();
506                b.write_str(value).ok();
507                b.write_str(unsafe { std::str::from_utf8_unchecked(misc::CRLF) })
508                    .ok();
509                let size = b.len();
510                self.b.replace(b);
511                (0, size)
512            }
513        };
514
515        self.indices.set(kh, self.pos.len() as u16);
516        let colon = offset + key.len();
517        self.pos
518            .push(Position::new(offset, size, colon, colon + 2, size - 2));
519        self
520    }
521
522    pub fn complete(mut self) -> Self {
523        match &mut self.b {
524            Some(b) => b.put_slice(misc::CRLF),
525            None => {
526                self.b.replace(BytesMut::from(misc::CRLF));
527            }
528        }
529        self
530    }
531
532    pub fn build(self) -> Headers {
533        let Self { b, pos, indices } = self;
534        Headers {
535            b: match b {
536                Some(b) => b.freeze(),
537                None => Bytes::new(),
538            },
539            pos,
540            indices,
541        }
542    }
543}
544
545pub struct Iter<'a> {
546    headers: &'a Headers,
547    cursor: usize,
548}
549
550/// Header represents a line of http header.
551/// For example:
552/// ```
553///    let raw_header_bytes = &b"Host: nginx/1.8.0\r\n"[..];
554/// ```
555pub struct Header<'a>(&'a [u8]);
556
557impl Header<'_> {
558    pub fn key(&self) -> &[u8] {
559        let pos = self.get_position();
560        &self.0[..pos]
561    }
562
563    pub fn value(&self) -> &[u8] {
564        let mut pos = self.get_position() + 1;
565        let raw = &self.0[pos..];
566        for (i, b) in raw.iter().enumerate() {
567            match b {
568                b' ' => (),
569                _ => {
570                    pos += i;
571                    break;
572                }
573            }
574        }
575        &self.0[pos..self.0.len() - 2]
576    }
577
578    pub fn key_str(&self) -> Cow<str> {
579        String::from_utf8_lossy(self.key())
580    }
581
582    pub fn value_str(&self) -> Cow<str> {
583        String::from_utf8_lossy(self.value())
584    }
585
586    #[inline]
587    fn get_position(&self) -> usize {
588        self.0.iter().position(|it| *it == b':').unwrap()
589    }
590}
591
592impl AsRef<[u8]> for Header<'_> {
593    fn as_ref(&self) -> &[u8] {
594        self.0
595    }
596}
597
598impl IntoIterator for Headers {
599    type Item = Bytes;
600    type IntoIter = RawHeaderIter;
601
602    fn into_iter(self) -> Self::IntoIter {
603        Self::IntoIter {
604            headers: self,
605            cur: 0,
606        }
607    }
608}
609
610pub struct RawHeaderIter {
611    headers: Headers,
612    cur: usize,
613}
614
615impl Iterator for RawHeaderIter {
616    type Item = Bytes;
617
618    fn next(&mut self) -> Option<Self::Item> {
619        match self.headers.pos.get(self.cur) {
620            None => None,
621            Some(pos) => {
622                let item = self.headers.b.split_to(pos.len());
623                self.cur += 1;
624                Some(item)
625            }
626        }
627    }
628}
629
630impl<'a> Iterator for Iter<'a> {
631    type Item = Header<'a>;
632
633    fn next(&mut self) -> Option<Self::Item> {
634        if self.cursor >= self.headers.pos.len() {
635            None
636        } else {
637            let pos = self.headers.pos.get(self.cursor).unwrap();
638            self.cursor += 1;
639            let b = &self.headers.b[pos.begin()..pos.end()];
640            Some(Header(b))
641        }
642    }
643}
644
645#[cfg(test)]
646mod http_headers_tests {
647    use super::*;
648
649    fn init() {
650        pretty_env_logger::try_init_timed().ok();
651    }
652
653    #[test]
654    fn test_malformed_headers() {
655        init();
656        let mut b = BytesMut::from(&b"x-your-header\r\n"[..]);
657
658        let res = Headers::read(&mut b, usize::MAX);
659        assert!(res.is_err());
660
661        let mut b = BytesMut::from(&b"Content-Type\x00: text/plain\r\n"[..]);
662        let res = Headers::read(&mut b, usize::MAX);
663        assert!(res.is_err());
664    }
665
666    #[test]
667    fn test_read_empty_headers() {
668        init();
669
670        let mut b = BytesMut::from(&b"\r\nFoo"[..]);
671
672        let res = Headers::read(&mut b, usize::MAX);
673        assert_eq!(3, b.len());
674        assert!(res.is_ok_and(|it| it.is_some_and(|it| {
675            if it.is_empty() {
676                let bb: Bytes = it.into();
677                bb.as_ref().eq(&b"\r\n"[..])
678            } else {
679                false
680            }
681        })));
682    }
683
684    #[test]
685    fn test_get() {
686        init();
687
688        let mut b = BytesMut::from(
689            &b"\
690        Accept: *\r\n\
691        foo:\r\n\
692        bar:123\r\n\
693        qux:    456\r\n\
694        blank:       \r\n\
695        dog:   \r  \n dog  \r   \r\n\
696        bear:   b e a r    \r\n\
697        space1: \r\n\
698        space2:  \r\n\
699        \r\n"[..],
700        );
701
702        let h = Headers::read_ext(&mut b, usize::MAX, true)
703            .unwrap()
704            .unwrap();
705        assert!(h.get("foo").is_some_and(|it| {
706            let s = it.as_ref();
707            s.is_empty()
708        }));
709        assert!(h.get("accept").is_some_and(|it| "*".eq(it.as_ref())));
710        assert!(h.get("bar").is_some_and(|it| "123".eq(it.as_ref())));
711        assert!(h.get("qux").is_some_and(|it| "456".eq(it.as_ref())));
712        assert!(h.get("blank").is_some_and(|it| "".eq(it.as_ref())));
713        assert!(h.get("dog").is_some_and(|it| "dog".eq(it.as_ref())));
714        assert!(h.get("bear").is_some_and(|it| "b e a r".eq(it.as_ref())));
715        assert!(h.get("space1").is_some_and(|it| "".eq(it.as_ref())));
716        assert!(h.get("space2").is_some_and(|it| "".eq(it.as_ref())));
717
718        assert!(h
719            .get_by_field(HttpField::Accept)
720            .is_some_and(|it| it.eq(&b"*"[..])));
721
722        info!("headers: {:?}", &h);
723    }
724
725    #[test]
726    fn test_read_partial_headers() {
727        init();
728
729        let mut b = BytesMut::from(&b"Host: localhost:8080\r\nContent-Type: text/html\r\nAcc"[..]);
730
731        let res = Headers::read(&mut b, usize::MAX);
732        assert!(res.is_ok_and(|h| h.is_none()), "not enough headers bytes");
733    }
734
735    #[test]
736    fn test_headers_positions() {
737        init();
738
739        let mut b = BytesMut::from(
740            &b"\
741        Host: localhost:8080\r\n\
742        Foo: 1\r\n\
743        Content-Type: text/html\r\n\
744        Foo: 2\r\n\
745        Accept: *\r\n\
746        \r\n"[..],
747        );
748
749        let headers = Headers::read(&mut b, usize::MAX).unwrap().unwrap();
750
751        let pos = headers.positions("fOo");
752        assert_eq!(2, pos.len());
753
754        let mut i = pos.into_iter();
755        let next = headers.nth(i.next().unwrap());
756        assert!(next.is_some_and(|(_, v)| b"1".eq(v)));
757        let next = headers.nth(i.next().unwrap());
758        assert!(next.is_some_and(|(_, v)| b"2".eq(v)));
759
760        assert!(i.next().is_none());
761    }
762
763    #[test]
764    fn test_read_complete_headers() {
765        init();
766
767        let origin = b"Host: localhost:8080\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nUser-Agent: HTTPie/3.2.2\r\n\r\nfoo";
768        let mut b = BytesMut::from(&origin[..]);
769
770        let res = Headers::read(&mut b, usize::MAX);
771        assert!(res.is_ok());
772        let res = res.unwrap();
773        assert!(res.is_some(), "part should not be None");
774        assert_eq!(3, b.len());
775        assert!(res.is_some(), "bad complete headers result");
776
777        let headers = res.unwrap();
778
779        assert_eq!(5, headers.len());
780
781        let check = |k: &str, v: &str| {
782            assert_eq!(Some(v.into()), headers.get(k).map(|it| it.to_string()));
783        };
784
785        check("host", "localhost:8080");
786        check("accept-encoding", "gzip, deflate");
787        check("accept", "*/*");
788        check("connection", "keep-alive");
789        check("user-agent", "HTTPie/3.2.2");
790
791        let chk = |header: Option<Header>, key: &str, val: &str| {
792            assert!(header.is_some());
793            let header = header.unwrap();
794            let k = header.key_str();
795            let v = header.value_str();
796            assert!(key.eq_ignore_ascii_case(k.as_ref()));
797            assert_eq!(val, v.as_ref());
798            assert_eq!(key.len() + 4 + val.len(), header.0.len());
799
800            let s = format!("{}: {}\r\n", key, val);
801            let raw: &[u8] = header.as_ref();
802            assert!(raw.eq_ignore_ascii_case(s.as_bytes()));
803        };
804
805        let mut hi = headers.iter();
806        chk(hi.next(), "host", "localhost:8080");
807        chk(hi.next(), "accept-encoding", "gzip, deflate");
808        chk(hi.next(), "accept", "*/*");
809        chk(hi.next(), "connection", "keep-alive");
810        chk(hi.next(), "user-agent", "HTTPie/3.2.2");
811        assert!(hi.next().is_none());
812        assert!(hi.next().is_none());
813
814        let chk2 = |header: Option<Bytes>, expect: &str| {
815            assert!(header.is_some());
816            let header = header.unwrap();
817            assert!(expect.as_bytes().eq_ignore_ascii_case(header.as_ref()));
818        };
819
820        let raw = headers.as_ref();
821        assert_eq!(&origin[..origin.len() - 3], raw);
822
823        let mut iter = headers.into_iter();
824
825        chk2(iter.next(), "host: localhost:8080\r\n");
826        chk2(iter.next(), "accept-encoding: gzip, deflate\r\n");
827        chk2(iter.next(), "accept: */*\r\n");
828        chk2(iter.next(), "connection: keep-alive\r\n");
829        chk2(iter.next(), "user-agent: HTTPie/3.2.2\r\n");
830        assert!(iter.next().is_none());
831        assert!(iter.next().is_none());
832    }
833
834    #[test]
835    fn test_build_headers() {
836        let raw = &b"foo: aaa\r\nbar: bbb\r\n\r\n"[..];
837        let mut b = BytesMut::from(raw);
838        let h0 = Headers::read(&mut b, usize::MAX).unwrap().unwrap();
839        assert!(b.is_empty());
840
841        let bu = Headers::builder()
842            .put("foo", "aaa")
843            .put("bar", "bbb")
844            .complete();
845        assert_eq!(2, bu.len());
846        let h = bu.build();
847
848        assert_eq!(&h0.pos, &h.pos, "generated positions should be same");
849
850        let s0 = unsafe { std::str::from_utf8_unchecked(h0.b.as_ref()) };
851        let s1 = unsafe { std::str::from_utf8_unchecked(h.b.as_ref()) };
852        assert_eq!(s0, s1);
853
854        let foo = h.get("foo").map(|it| it.to_string());
855        let bar = h.get("bar").map(|it| it.to_string());
856        let qux = h.get("qux").map(|it| it.to_string());
857
858        assert_eq!(Some("aaa".to_string()), foo);
859        assert_eq!(Some("bbb".to_string()), bar);
860        assert!(qux.is_none());
861
862        assert!(h.nth(0).is_some_and(|(k, v)| k.eq(b"foo") && v.eq(b"aaa")));
863        assert!(h.nth(1).is_some_and(|(k, v)| k.eq(b"bar") && v.eq(b"bbb")));
864        assert!(h.nth(2).is_none());
865    }
866}