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