1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_duration_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::needless_lifetimes)] #![cfg_attr(
48 not(all(feature = "full", feature = "experimental")),
49 allow(unused_imports)
50)]
51
52mod err;
53pub mod request;
54mod response;
55mod util;
56
57use tor_circmgr::{CircMgr, DirInfo};
58use tor_error::bad_api_usage;
59use tor_rtcompat::{Runtime, SleepProvider, SleepProviderExt};
60
61#[cfg(feature = "xz")]
63use async_compression::futures::bufread::XzDecoder;
64use async_compression::futures::bufread::ZlibDecoder;
65#[cfg(feature = "zstd")]
66use async_compression::futures::bufread::ZstdDecoder;
67
68use futures::io::{
69 AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader,
70};
71use futures::FutureExt;
72use memchr::memchr;
73use std::sync::Arc;
74use std::time::Duration;
75use tracing::info;
76
77pub use err::{Error, RequestError, RequestFailedError};
78pub use response::{DirResponse, SourceInfo};
79
80pub type Result<T> = std::result::Result<T, Error>;
82
83pub type RequestResult<T> = std::result::Result<T, RequestError>;
85
86#[derive(Copy, Clone, Debug, Eq, PartialEq)]
92#[non_exhaustive]
93pub enum AnonymizedRequest {
94 Anonymized,
96 Direct,
98}
99
100pub async fn get_resource<CR, R, SP>(
113 req: &CR,
114 dirinfo: DirInfo<'_>,
115 runtime: &SP,
116 circ_mgr: Arc<CircMgr<R>>,
117) -> Result<DirResponse>
118where
119 CR: request::Requestable + ?Sized,
120 R: Runtime,
121 SP: SleepProvider,
122{
123 let circuit = circ_mgr.get_or_launch_dir(dirinfo).await?;
124
125 if req.anonymized() == AnonymizedRequest::Anonymized {
126 return Err(bad_api_usage!("Tried to use get_resource for an anonymized request").into());
127 }
128
129 let begin_timeout = Duration::from_secs(5);
131 let source = match SourceInfo::from_circuit(&circuit) {
132 Ok(source) => source,
133 Err(e) => {
134 return Err(Error::RequestFailed(RequestFailedError {
135 source: None,
136 error: e.into(),
137 }));
138 }
139 };
140
141 let wrap_err = |error| {
142 Error::RequestFailed(RequestFailedError {
143 source: Some(source.clone()),
144 error,
145 })
146 };
147
148 req.check_circuit(&circuit).await.map_err(wrap_err)?;
149
150 let mut stream = runtime
152 .timeout(begin_timeout, circuit.begin_dir_stream())
153 .await
154 .map_err(RequestError::from)
155 .map_err(wrap_err)?
156 .map_err(RequestError::from)
157 .map_err(wrap_err)?; let r = send_request(runtime, req, &mut stream, Some(source.clone())).await;
162
163 if should_retire_circ(&r) {
164 retire_circ(&circ_mgr, &source, "Partial response");
165 }
166
167 r
168}
169
170fn should_retire_circ(result: &Result<DirResponse>) -> bool {
173 match result {
174 Err(e) => e.should_retire_circ(),
175 Ok(dr) => dr.error().map(RequestError::should_retire_circ) == Some(true),
176 }
177}
178
179#[deprecated(since = "0.8.1", note = "Use send_request instead.")]
181pub async fn download<R, S, SP>(
182 runtime: &SP,
183 req: &R,
184 stream: &mut S,
185 source: Option<SourceInfo>,
186) -> Result<DirResponse>
187where
188 R: request::Requestable + ?Sized,
189 S: AsyncRead + AsyncWrite + Send + Unpin,
190 SP: SleepProvider,
191{
192 send_request(runtime, req, stream, source).await
193}
194
195pub async fn send_request<R, S, SP>(
214 runtime: &SP,
215 req: &R,
216 stream: &mut S,
217 source: Option<SourceInfo>,
218) -> Result<DirResponse>
219where
220 R: request::Requestable + ?Sized,
221 S: AsyncRead + AsyncWrite + Send + Unpin,
222 SP: SleepProvider,
223{
224 let wrap_err = |error| {
225 Error::RequestFailed(RequestFailedError {
226 source: source.clone(),
227 error,
228 })
229 };
230
231 let partial_ok = req.partial_response_body_ok();
232 let maxlen = req.max_response_len();
233 let anonymized = req.anonymized();
234 let req = req.make_request().map_err(wrap_err)?;
235 let encoded = util::encode_request(&req);
236
237 stream
239 .write_all(encoded.as_bytes())
240 .await
241 .map_err(RequestError::from)
242 .map_err(wrap_err)?;
243 stream
244 .flush()
245 .await
246 .map_err(RequestError::from)
247 .map_err(wrap_err)?;
248
249 let mut buffered = BufReader::new(stream);
250
251 let header = read_headers(&mut buffered).await.map_err(wrap_err)?;
254 if header.status != Some(200) {
255 return Ok(DirResponse::new(
256 header.status.unwrap_or(0),
257 header.status_message,
258 None,
259 vec![],
260 source,
261 ));
262 }
263
264 let mut decoder =
265 get_decoder(buffered, header.encoding.as_deref(), anonymized).map_err(wrap_err)?;
266
267 let mut result = Vec::new();
268 let ok = read_and_decompress(runtime, &mut decoder, maxlen, &mut result).await;
269
270 let ok = match (partial_ok, ok, result.len()) {
271 (true, Err(e), n) if n > 0 => {
272 Err(e)
274 }
275 (_, Err(e), _) => {
276 return Err(wrap_err(e));
277 }
278 (_, Ok(()), _) => Ok(()),
279 };
280
281 Ok(DirResponse::new(200, None, ok.err(), result, source))
282}
283
284async fn read_headers<S>(stream: &mut S) -> RequestResult<HeaderStatus>
286where
287 S: AsyncBufRead + Unpin,
288{
289 let mut buf = Vec::with_capacity(1024);
290
291 loop {
292 let n = read_until_limited(stream, b'\n', 2048, &mut buf).await?;
296
297 let mut headers = [httparse::EMPTY_HEADER; 32];
299 let mut response = httparse::Response::new(&mut headers);
300
301 match response.parse(&buf[..])? {
302 httparse::Status::Partial => {
303 if n == 0 {
306 return Err(RequestError::TruncatedHeaders);
308 }
309
310 if buf.len() >= 16384 {
312 return Err(httparse::Error::TooManyHeaders.into());
313 }
314 }
315 httparse::Status::Complete(n_parsed) => {
316 if response.code != Some(200) {
317 return Ok(HeaderStatus {
318 status: response.code,
319 status_message: response.reason.map(str::to_owned),
320 encoding: None,
321 });
322 }
323 let encoding = if let Some(enc) = response
324 .headers
325 .iter()
326 .find(|h| h.name == "Content-Encoding")
327 {
328 Some(String::from_utf8(enc.value.to_vec())?)
329 } else {
330 None
331 };
332 assert!(n_parsed == buf.len());
339 return Ok(HeaderStatus {
340 status: Some(200),
341 status_message: None,
342 encoding,
343 });
344 }
345 }
346 if n == 0 {
347 return Err(RequestError::TruncatedHeaders);
348 }
349 }
350}
351
352#[derive(Debug, Clone)]
354struct HeaderStatus {
355 status: Option<u16>,
357 status_message: Option<String>,
359 encoding: Option<String>,
361}
362
363async fn read_and_decompress<S, SP>(
372 runtime: &SP,
373 mut stream: S,
374 maxlen: usize,
375 result: &mut Vec<u8>,
376) -> RequestResult<()>
377where
378 S: AsyncRead + Unpin,
379 SP: SleepProvider,
380{
381 let buffer_window_size = 1024;
382 let mut written_total: usize = 0;
383 let read_timeout = Duration::from_secs(10);
386 let timer = runtime.sleep(read_timeout).fuse();
387 futures::pin_mut!(timer);
388
389 loop {
390 result.resize(written_total + buffer_window_size, 0);
392 let buf: &mut [u8] = &mut result[written_total..written_total + buffer_window_size];
393
394 let status = futures::select! {
395 status = stream.read(buf).fuse() => status,
396 _ = timer => {
397 result.resize(written_total, 0); return Err(RequestError::DirTimeout);
399 }
400 };
401 let written_in_this_loop = match status {
402 Ok(n) => n,
403 Err(other) => {
404 result.resize(written_total, 0); return Err(other.into());
406 }
407 };
408
409 written_total += written_in_this_loop;
410
411 if written_in_this_loop == 0 {
414 if written_total < result.len() {
420 result.resize(written_total, 0);
421 }
422 return Ok(());
423 }
424
425 if written_total > maxlen {
431 result.resize(maxlen, 0);
432 return Err(RequestError::ResponseTooLong(written_total));
433 }
434 }
435}
436
437fn retire_circ<R>(circ_mgr: &Arc<CircMgr<R>>, source_info: &SourceInfo, error: &str)
439where
440 R: Runtime,
441{
442 let id = source_info.unique_circ_id();
443 info!(
444 "{}: Retiring circuit because of directory failure: {}",
445 &id, &error
446 );
447 circ_mgr.retire_circ(id);
448}
449
450async fn read_until_limited<S>(
457 stream: &mut S,
458 byte: u8,
459 max: usize,
460 buf: &mut Vec<u8>,
461) -> std::io::Result<usize>
462where
463 S: AsyncBufRead + Unpin,
464{
465 let mut n_added = 0;
466 loop {
467 let data = stream.fill_buf().await?;
468 if data.is_empty() {
469 return Ok(n_added);
471 }
472 debug_assert!(n_added < max);
473 let remaining_space = max - n_added;
474 let (available, found_byte) = match memchr(byte, data) {
475 Some(idx) => (idx + 1, true),
476 None => (data.len(), false),
477 };
478 debug_assert!(available >= 1);
479 let n_to_copy = std::cmp::min(remaining_space, available);
480 buf.extend(&data[..n_to_copy]);
481 stream.consume_unpin(n_to_copy);
482 n_added += n_to_copy;
483 if found_byte || n_added == max {
484 return Ok(n_added);
485 }
486 }
487}
488
489macro_rules! decoder {
491 ($dec:ident, $s:expr) => {{
492 let mut decoder = $dec::new($s);
493 decoder.multiple_members(true);
494 Ok(Box::new(decoder))
495 }};
496}
497
498fn get_decoder<'a, S: AsyncBufRead + Unpin + Send + 'a>(
501 stream: S,
502 encoding: Option<&str>,
503 anonymized: AnonymizedRequest,
504) -> RequestResult<Box<dyn AsyncRead + Unpin + Send + 'a>> {
505 use AnonymizedRequest::Direct;
506 match (encoding, anonymized) {
507 (None | Some("identity"), _) => Ok(Box::new(stream)),
508 (Some("deflate"), _) => decoder!(ZlibDecoder, stream),
509 #[cfg(feature = "xz")]
513 (Some("x-tor-lzma"), Direct) => decoder!(XzDecoder, stream),
514 #[cfg(feature = "zstd")]
515 (Some("x-zstd"), Direct) => decoder!(ZstdDecoder, stream),
516 (Some(other), _) => Err(RequestError::ContentEncoding(other.into())),
517 }
518}
519
520#[cfg(test)]
521mod test {
522 #![allow(clippy::bool_assert_comparison)]
524 #![allow(clippy::clone_on_copy)]
525 #![allow(clippy::dbg_macro)]
526 #![allow(clippy::mixed_attributes_style)]
527 #![allow(clippy::print_stderr)]
528 #![allow(clippy::print_stdout)]
529 #![allow(clippy::single_char_pattern)]
530 #![allow(clippy::unwrap_used)]
531 #![allow(clippy::unchecked_duration_subtraction)]
532 #![allow(clippy::useless_vec)]
533 #![allow(clippy::needless_pass_by_value)]
534 use super::*;
536 use tor_rtmock::io::stream_pair;
537
538 #[allow(deprecated)] use tor_rtmock::time::MockSleepProvider;
540
541 use futures_await_test::async_test;
542
543 #[async_test]
544 async fn test_read_until_limited() -> RequestResult<()> {
545 let mut out = Vec::new();
546 let bytes = b"This line eventually ends\nthen comes another\n";
547
548 let mut s = &bytes[..];
550 let res = read_until_limited(&mut s, b'\n', 100, &mut out).await;
551 assert_eq!(res?, 26);
552 assert_eq!(&out[..], b"This line eventually ends\n");
553
554 let mut s = &bytes[..];
556 out.clear();
557 let res = read_until_limited(&mut s, b'\n', 10, &mut out).await;
558 assert_eq!(res?, 10);
559 assert_eq!(&out[..], b"This line ");
560
561 let mut s = &bytes[..];
563 out.clear();
564 let res = read_until_limited(&mut s, b'Z', 100, &mut out).await;
565 assert_eq!(res?, 45);
566 assert_eq!(&out[..], &bytes[..]);
567
568 Ok(())
569 }
570
571 async fn decomp_basic(
573 encoding: Option<&str>,
574 data: &[u8],
575 maxlen: usize,
576 ) -> (RequestResult<()>, Vec<u8>) {
577 #[allow(deprecated)] let mock_time = MockSleepProvider::new(std::time::SystemTime::now());
581
582 let mut output = Vec::new();
583 let mut stream = match get_decoder(data, encoding, AnonymizedRequest::Direct) {
584 Ok(s) => s,
585 Err(e) => return (Err(e), output),
586 };
587
588 let r = read_and_decompress(&mock_time, &mut stream, maxlen, &mut output).await;
589
590 (r, output)
591 }
592
593 #[async_test]
594 async fn decompress_identity() -> RequestResult<()> {
595 let mut text = Vec::new();
596 for _ in 0..1000 {
597 text.extend(b"This is a string with a nontrivial length that we'll use to make sure that the loop is executed more than once.");
598 }
599
600 let limit = 10 << 20;
601 let (s, r) = decomp_basic(None, &text[..], limit).await;
602 s?;
603 assert_eq!(r, text);
604
605 let (s, r) = decomp_basic(Some("identity"), &text[..], limit).await;
606 s?;
607 assert_eq!(r, text);
608
609 let limit = 100;
611 let (s, r) = decomp_basic(Some("identity"), &text[..], limit).await;
612 assert!(s.is_err());
613 assert_eq!(r, &text[..100]);
614
615 Ok(())
616 }
617
618 #[async_test]
619 async fn decomp_zlib() -> RequestResult<()> {
620 let compressed =
621 hex::decode("789cf3cf4b5548cb2cce500829cf8730825253200ca79c52881c00e5970c88").unwrap();
622
623 let limit = 10 << 20;
624 let (s, r) = decomp_basic(Some("deflate"), &compressed, limit).await;
625 s?;
626 assert_eq!(r, b"One fish Two fish Red fish Blue fish");
627
628 Ok(())
629 }
630
631 #[cfg(feature = "zstd")]
632 #[async_test]
633 async fn decomp_zstd() -> RequestResult<()> {
634 let compressed = hex::decode("28b52ffd24250d0100c84f6e6520666973682054776f526564426c756520666973680a0200600c0e2509478352cb").unwrap();
635 let limit = 10 << 20;
636 let (s, r) = decomp_basic(Some("x-zstd"), &compressed, limit).await;
637 s?;
638 assert_eq!(r, b"One fish Two fish Red fish Blue fish\n");
639
640 Ok(())
641 }
642
643 #[cfg(feature = "xz")]
644 #[async_test]
645 async fn decomp_xz2() -> RequestResult<()> {
646 let compressed = hex::decode("fd377a585a000004e6d6b446020021011c00000010cf58cce00024001d5d00279b88a202ca8612cfb3c19c87c34248a570451e4851d3323d34ab8000000000000901af64854c91f600013925d6ec06651fb6f37d010000000004595a").unwrap();
648 let limit = 10 << 20;
649 let (s, r) = decomp_basic(Some("x-tor-lzma"), &compressed, limit).await;
650 s?;
651 assert_eq!(r, b"One fish Two fish Red fish Blue fish\n");
652
653 Ok(())
654 }
655
656 #[async_test]
657 async fn decomp_unknown() {
658 let compressed = hex::decode("28b52ffd24250d0100c84f6e6520666973682054776f526564426c756520666973680a0200600c0e2509478352cb").unwrap();
659 let limit = 10 << 20;
660 let (s, _r) = decomp_basic(Some("x-proprietary-rle"), &compressed, limit).await;
661
662 assert!(matches!(s, Err(RequestError::ContentEncoding(_))));
663 }
664
665 #[async_test]
666 async fn decomp_bad_data() {
667 let compressed = b"This is not good zlib data";
668 let limit = 10 << 20;
669 let (s, _r) = decomp_basic(Some("deflate"), compressed, limit).await;
670
671 assert!(matches!(s, Err(RequestError::IoError(_))));
673 }
674
675 #[async_test]
676 async fn headers_ok() -> RequestResult<()> {
677 let text = b"HTTP/1.0 200 OK\r\nDate: ignored\r\nContent-Encoding: Waffles\r\n\r\n";
678
679 let mut s = &text[..];
680 let h = read_headers(&mut s).await?;
681
682 assert_eq!(h.status, Some(200));
683 assert_eq!(h.encoding.as_deref(), Some("Waffles"));
684
685 let mut s = &text[..15];
687 let h = read_headers(&mut s).await;
688 assert!(matches!(h, Err(RequestError::TruncatedHeaders)));
689
690 let text = b"HTTP/1.0 404 Not found\r\n\r\n";
692 let mut s = &text[..];
693 let h = read_headers(&mut s).await?;
694
695 assert_eq!(h.status, Some(404));
696 assert!(h.encoding.is_none());
697
698 Ok(())
699 }
700
701 #[async_test]
702 async fn headers_bogus() -> Result<()> {
703 let text = b"HTTP/999.0 WHAT EVEN\r\n\r\n";
704 let mut s = &text[..];
705 let h = read_headers(&mut s).await;
706
707 assert!(h.is_err());
708 assert!(matches!(h, Err(RequestError::HttparseError(_))));
709 Ok(())
710 }
711
712 fn run_download_test<Req: request::Requestable>(
718 req: Req,
719 response: &[u8],
720 ) -> (Result<DirResponse>, RequestResult<Vec<u8>>) {
721 let (mut s1, s2) = stream_pair();
722 let (mut s2_r, mut s2_w) = s2.split();
723
724 tor_rtcompat::test_with_one_runtime!(|rt| async move {
725 let rt2 = rt.clone();
726 let (v1, v2, v3): (
727 Result<DirResponse>,
728 RequestResult<Vec<u8>>,
729 RequestResult<()>,
730 ) = futures::join!(
731 async {
732 let r = send_request(&rt, &req, &mut s1, None).await;
734 s1.close().await.map_err(|error| {
735 Error::RequestFailed(RequestFailedError {
736 source: None,
737 error: error.into(),
738 })
739 })?;
740 r
741 },
742 async {
743 let mut v = Vec::new();
745 s2_r.read_to_end(&mut v).await?;
746 Ok(v)
747 },
748 async {
749 s2_w.write_all(response).await?;
751 rt2.sleep(Duration::from_millis(50)).await;
764 s2_w.close().await?;
765 Ok(())
766 }
767 );
768
769 assert!(v3.is_ok());
770
771 (v1, v2)
772 })
773 }
774
775 #[test]
776 fn test_send_request() -> RequestResult<()> {
777 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
778
779 let (response, request) = run_download_test(
780 req,
781 b"HTTP/1.0 200 OK\r\n\r\nThis is where the descs would go.",
782 );
783
784 let request = request?;
785 assert!(request[..].starts_with(
786 b"GET /tor/micro/d/CQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQk.z HTTP/1.0\r\n"
787 ));
788
789 let response = response.unwrap();
790 assert_eq!(response.status_code(), 200);
791 assert!(!response.is_partial());
792 assert!(response.error().is_none());
793 assert!(response.source().is_none());
794 let out_ref = response.output_unchecked();
795 assert_eq!(out_ref, b"This is where the descs would go.");
796 let out = response.into_output_unchecked();
797 assert_eq!(&out, b"This is where the descs would go.");
798
799 Ok(())
800 }
801
802 #[test]
803 fn test_download_truncated() {
804 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
806 let mut response_text: Vec<u8> =
807 (*b"HTTP/1.0 200 OK\r\nContent-Encoding: deflate\r\n\r\n").into();
808 response_text.extend(
810 hex::decode("789cf3cf4b5548cb2cce500829cf8730825253200ca79c52881c00e5970c88").unwrap(),
811 );
812 response_text.extend(
813 hex::decode("789cf3cf4b5548cb2cce500829cf8730825253200ca79c52881c00e5").unwrap(),
814 );
815 let (response, request) = run_download_test(req, &response_text);
816 assert!(request.is_ok());
817 assert!(response.is_err()); let req: request::MicrodescRequest = vec![[9; 32]; 2].into_iter().collect();
821
822 let (response, request) = run_download_test(req, &response_text);
823 assert!(request.is_ok());
824
825 let response = response.unwrap();
826 assert_eq!(response.status_code(), 200);
827 assert!(response.error().is_some());
828 assert!(response.is_partial());
829 assert!(response.output_unchecked().len() < 37 * 2);
830 assert!(response.output_unchecked().starts_with(b"One fish"));
831 }
832
833 #[test]
834 fn test_404() {
835 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
836 let response_text = b"HTTP/1.0 418 I'm a teapot\r\n\r\n";
837 let (response, _request) = run_download_test(req, response_text);
838
839 assert_eq!(response.unwrap().status_code(), 418);
840 }
841
842 #[test]
843 fn test_headers_truncated() {
844 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
845 let response_text = b"HTTP/1.0 404 truncation happens here\r\n";
846 let (response, _request) = run_download_test(req, response_text);
847
848 assert!(matches!(
849 response,
850 Err(Error::RequestFailed(RequestFailedError {
851 error: RequestError::TruncatedHeaders,
852 ..
853 }))
854 ));
855
856 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
858 let response_text = b"";
859 let (response, _request) = run_download_test(req, response_text);
860
861 assert!(matches!(
862 response,
863 Err(Error::RequestFailed(RequestFailedError {
864 error: RequestError::TruncatedHeaders,
865 ..
866 }))
867 ));
868 }
869
870 #[test]
871 fn test_headers_too_long() {
872 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
873 let mut response_text: Vec<u8> = (*b"HTTP/1.0 418 I'm a teapot\r\nX-Too-Many-As: ").into();
874 response_text.resize(16384, b'A');
875 let (response, _request) = run_download_test(req, &response_text);
876
877 assert!(response.as_ref().unwrap_err().should_retire_circ());
878 assert!(matches!(
879 response,
880 Err(Error::RequestFailed(RequestFailedError {
881 error: RequestError::HttparseError(_),
882 ..
883 }))
884 ));
885 }
886
887 }