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#[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
98pub struct Headers {
100 pub(crate) b: Bytes,
102 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 if i - offset == 1 {
184 eof = true;
185 break;
186 }
187 return Err(MalformedHttpPacket("incomplete CRLF".into()));
188 }
189 }
190 other => {
191 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 hash = (hash << 5).wrapping_sub(hash);
201
202 if other.is_ascii_uppercase() {
203 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 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 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 #[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
550pub 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}