1#![cfg_attr(docsrs, feature(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_time_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)] #![allow(clippy::collapsible_if)] #![deny(clippy::unused_async)]
47#![cfg_attr(
51 not(all(feature = "full", feature = "experimental")),
52 allow(unused_imports)
53)]
54
55mod body;
56mod err;
57pub mod request;
58mod response;
59mod util;
60
61use tor_circmgr::{CircMgr, DirInfo};
62use tor_error::bad_api_usage;
63use tor_rtcompat::{Runtime, SleepProvider, SleepProviderExt};
64
65#[cfg(feature = "xz")]
67use async_compression::futures::bufread::XzDecoder;
68use async_compression::futures::bufread::ZlibDecoder;
69#[cfg(feature = "zstd")]
70use async_compression::futures::bufread::ZstdDecoder;
71
72use futures::FutureExt;
73use futures::io::{
74 AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader,
75};
76use memchr::memchr;
77use std::sync::Arc;
78use std::time::Duration;
79use tracing::{info, instrument};
80
81pub use err::{Error, RequestError, RequestFailedError};
82pub use response::{DirResponse, SourceInfo};
83
84pub type Result<T> = std::result::Result<T, Error>;
86
87pub type RequestResult<T> = std::result::Result<T, RequestError>;
89
90#[derive(Copy, Clone, Debug, Eq, PartialEq)]
117#[non_exhaustive]
118pub enum AnonymizedRequest {
119 Anonymized,
129
130 Direct,
143}
144
145#[instrument(level = "trace", skip_all)]
158pub async fn get_resource<CR, R, SP>(
159 req: &CR,
160 dirinfo: DirInfo<'_>,
161 runtime: &SP,
162 circ_mgr: Arc<CircMgr<R>>,
163) -> Result<DirResponse>
164where
165 CR: request::Requestable + ?Sized,
166 R: Runtime,
167 SP: SleepProvider,
168{
169 let tunnel = circ_mgr.get_or_launch_dir(dirinfo).await?;
170
171 if req.anonymized() == AnonymizedRequest::Anonymized {
172 return Err(bad_api_usage!("Tried to use get_resource for an anonymized request").into());
173 }
174
175 let begin_timeout = Duration::from_secs(5);
177 let source = match SourceInfo::from_tunnel(&tunnel) {
178 Ok(source) => source,
179 Err(e) => {
180 return Err(Error::RequestFailed(RequestFailedError {
181 source: None,
182 error: e.into(),
183 }));
184 }
185 };
186
187 let wrap_err = |error| {
188 Error::RequestFailed(RequestFailedError {
189 source: source.clone(),
190 error,
191 })
192 };
193
194 req.check_circuit(&tunnel).await.map_err(wrap_err)?;
195
196 let mut stream = runtime
198 .timeout(begin_timeout, tunnel.begin_dir_stream())
199 .await
200 .map_err(RequestError::from)
201 .map_err(wrap_err)?
202 .map_err(RequestError::from)
203 .map_err(wrap_err)?; let r = send_request(runtime, req, &mut stream, source.clone()).await;
208
209 if should_retire_circ(&r) {
210 retire_circ(&circ_mgr, &tunnel.unique_id(), "Partial response");
211 }
212
213 r
214}
215
216fn should_retire_circ(result: &Result<DirResponse>) -> bool {
219 match result {
220 Err(e) => e.should_retire_circ(),
221 Ok(dr) => dr.error().map(RequestError::should_retire_circ) == Some(true),
222 }
223}
224
225#[deprecated(since = "0.8.1", note = "Use send_request instead.")]
227pub async fn download<R, S, SP>(
228 runtime: &SP,
229 req: &R,
230 stream: &mut S,
231 source: Option<SourceInfo>,
232) -> Result<DirResponse>
233where
234 R: request::Requestable + ?Sized,
235 S: AsyncRead + AsyncWrite + Send + Unpin,
236 SP: SleepProvider,
237{
238 send_request(runtime, req, stream, source).await
239}
240
241pub async fn send_request<R, S, SP>(
260 runtime: &SP,
261 req: &R,
262 stream: &mut S,
263 source: Option<SourceInfo>,
264) -> Result<DirResponse>
265where
266 R: request::Requestable + ?Sized,
267 S: AsyncRead + AsyncWrite + Send + Unpin,
268 SP: SleepProvider,
269{
270 let wrap_err = |error| {
271 Error::RequestFailed(RequestFailedError {
272 source: source.clone(),
273 error,
274 })
275 };
276
277 let partial_ok = req.partial_response_body_ok();
278 let maxlen = req.max_response_len();
279 let anonymized = req.anonymized();
280 let req = req.make_request().map_err(wrap_err)?;
281 let method = req.method().clone();
282 let encoded = util::encode_request(&req);
283
284 for chunk in encoded.iter() {
286 stream
287 .write_all(chunk)
288 .await
289 .map_err(RequestError::from)
290 .map_err(wrap_err)?;
291 }
292 stream
293 .flush()
294 .await
295 .map_err(RequestError::from)
296 .map_err(wrap_err)?;
297
298 let mut buffered = BufReader::new(stream);
299
300 let header = read_headers(&mut buffered).await.map_err(wrap_err)?;
303 if header.status != Some(200) {
304 return Ok(DirResponse::new(
305 method,
306 header.status.unwrap_or(0),
307 header.status_message,
308 None,
309 vec![],
310 source,
311 ));
312 }
313
314 let mut decoder =
315 get_decoder(buffered, header.encoding.as_deref(), anonymized).map_err(wrap_err)?;
316
317 let mut result = Vec::new();
318 let ok = read_and_decompress(runtime, &mut decoder, maxlen, &mut result).await;
319
320 let ok = match (partial_ok, ok, result.len()) {
321 (true, Err(e), n) if n > 0 => {
322 Err(e)
324 }
325 (_, Err(e), _) => {
326 return Err(wrap_err(e));
327 }
328 (_, Ok(()), _) => Ok(()),
329 };
330
331 Ok(DirResponse::new(
332 method,
333 200,
334 None,
335 ok.err(),
336 result,
337 source,
338 ))
339}
340
341const MAX_HEADERS_LEN: usize = 16384;
345
346async fn read_headers<S>(stream: &mut S) -> RequestResult<HeaderStatus>
348where
349 S: AsyncBufRead + Unpin,
350{
351 let mut buf = Vec::with_capacity(1024);
352
353 loop {
354 let n = read_until_limited(stream, b'\n', 2048, &mut buf).await?;
358
359 let mut headers = [httparse::EMPTY_HEADER; 32];
361 let mut response = httparse::Response::new(&mut headers);
362
363 match response.parse(&buf[..])? {
364 httparse::Status::Partial => {
365 if n == 0 {
368 return Err(RequestError::TruncatedHeaders);
370 }
371
372 if buf.len() >= MAX_HEADERS_LEN {
373 return Err(RequestError::HeadersTooLong(buf.len()));
374 }
375 }
376 httparse::Status::Complete(n_parsed) => {
377 if response.code != Some(200) {
378 return Ok(HeaderStatus {
379 status: response.code,
380 status_message: response.reason.map(str::to_owned),
381 encoding: None,
382 });
383 }
384 let encoding = if let Some(enc) = response
385 .headers
386 .iter()
387 .find(|h| h.name == "Content-Encoding")
388 {
389 Some(String::from_utf8(enc.value.to_vec())?)
390 } else {
391 None
392 };
393 assert!(n_parsed == buf.len());
400 return Ok(HeaderStatus {
401 status: Some(200),
402 status_message: None,
403 encoding,
404 });
405 }
406 }
407 if n == 0 {
408 return Err(RequestError::TruncatedHeaders);
409 }
410 }
411}
412
413#[derive(Debug, Clone)]
415struct HeaderStatus {
416 status: Option<u16>,
418 status_message: Option<String>,
420 encoding: Option<String>,
422}
423
424async fn read_and_decompress<S, SP>(
433 runtime: &SP,
434 mut stream: S,
435 maxlen: usize,
436 result: &mut Vec<u8>,
437) -> RequestResult<()>
438where
439 S: AsyncRead + Unpin,
440 SP: SleepProvider,
441{
442 let buffer_window_size = 1024;
443 let mut written_total: usize = 0;
444 let read_timeout = Duration::from_secs(10);
447 let timer = runtime.sleep(read_timeout).fuse();
448 futures::pin_mut!(timer);
449
450 loop {
451 result.resize(written_total + buffer_window_size, 0);
453 let buf: &mut [u8] = &mut result[written_total..written_total + buffer_window_size];
454
455 let status = futures::select! {
456 status = stream.read(buf).fuse() => status,
457 _ = timer => {
458 result.resize(written_total, 0); return Err(RequestError::DirTimeout);
460 }
461 };
462 let written_in_this_loop = match status {
463 Ok(n) => n,
464 Err(other) => {
465 result.resize(written_total, 0); return Err(other.into());
467 }
468 };
469
470 written_total += written_in_this_loop;
471
472 if written_in_this_loop == 0 {
475 if written_total < result.len() {
481 result.resize(written_total, 0);
482 }
483 return Ok(());
484 }
485
486 if written_total > maxlen {
492 result.resize(maxlen, 0);
493 return Err(RequestError::ResponseTooLong(written_total));
494 }
495 }
496}
497
498fn retire_circ<R>(circ_mgr: &Arc<CircMgr<R>>, id: &tor_proto::circuit::UniqId, error: &str)
500where
501 R: Runtime,
502{
503 info!(
504 "{}: Retiring circuit because of directory failure: {}",
505 &id, &error
506 );
507 circ_mgr.retire_circ(id);
508}
509
510async fn read_until_limited<S>(
517 stream: &mut S,
518 byte: u8,
519 max: usize,
520 buf: &mut Vec<u8>,
521) -> std::io::Result<usize>
522where
523 S: AsyncBufRead + Unpin,
524{
525 let mut n_added = 0;
526 loop {
527 let data = stream.fill_buf().await?;
528 if data.is_empty() {
529 return Ok(n_added);
531 }
532 debug_assert!(n_added < max);
533 let remaining_space = max - n_added;
534 let (available, found_byte) = match memchr(byte, data) {
535 Some(idx) => (idx + 1, true),
536 None => (data.len(), false),
537 };
538 debug_assert!(available >= 1);
539 let n_to_copy = std::cmp::min(remaining_space, available);
540 buf.extend(&data[..n_to_copy]);
541 stream.consume_unpin(n_to_copy);
542 n_added += n_to_copy;
543 if found_byte || n_added == max {
544 return Ok(n_added);
545 }
546 }
547}
548
549macro_rules! decoder {
551 ($dec:ident, $s:expr) => {{
552 let mut decoder = $dec::new($s);
553 decoder.multiple_members(true);
554 Ok(Box::new(decoder))
555 }};
556}
557
558fn get_decoder<'a, S: AsyncBufRead + Unpin + Send + 'a>(
561 stream: S,
562 encoding: Option<&str>,
563 anonymized: AnonymizedRequest,
564) -> RequestResult<Box<dyn AsyncRead + Unpin + Send + 'a>> {
565 use AnonymizedRequest::Direct;
566 match (encoding, anonymized) {
567 (None | Some("identity"), _) => Ok(Box::new(stream)),
568 (Some("deflate"), _) => decoder!(ZlibDecoder, stream),
569 #[cfg(feature = "xz")]
573 (Some("x-tor-lzma"), Direct) => decoder!(XzDecoder, stream),
574 #[cfg(feature = "zstd")]
575 (Some("x-zstd"), Direct) => decoder!(ZstdDecoder, stream),
576 (Some(other), _) => Err(RequestError::ContentEncoding(other.into())),
577 }
578}
579
580#[cfg(test)]
581mod test {
582 #![allow(clippy::bool_assert_comparison)]
584 #![allow(clippy::clone_on_copy)]
585 #![allow(clippy::dbg_macro)]
586 #![allow(clippy::mixed_attributes_style)]
587 #![allow(clippy::print_stderr)]
588 #![allow(clippy::print_stdout)]
589 #![allow(clippy::single_char_pattern)]
590 #![allow(clippy::unwrap_used)]
591 #![allow(clippy::unchecked_time_subtraction)]
592 #![allow(clippy::useless_vec)]
593 #![allow(clippy::needless_pass_by_value)]
594 use super::*;
596 use tor_rtmock::io::stream_pair;
597
598 use tor_rtmock::simple_time::SimpleMockTimeProvider;
599 use web_time_compat::{SystemTime, SystemTimeExt};
600
601 use futures_await_test::async_test;
602
603 #[async_test]
604 async fn test_read_until_limited() -> RequestResult<()> {
605 let mut out = Vec::new();
606 let bytes = b"This line eventually ends\nthen comes another\n";
607
608 let mut s = &bytes[..];
610 let res = read_until_limited(&mut s, b'\n', 100, &mut out).await;
611 assert_eq!(res?, 26);
612 assert_eq!(&out[..], b"This line eventually ends\n");
613
614 let mut s = &bytes[..];
616 out.clear();
617 let res = read_until_limited(&mut s, b'\n', 10, &mut out).await;
618 assert_eq!(res?, 10);
619 assert_eq!(&out[..], b"This line ");
620
621 let mut s = &bytes[..];
623 out.clear();
624 let res = read_until_limited(&mut s, b'Z', 100, &mut out).await;
625 assert_eq!(res?, 45);
626 assert_eq!(&out[..], &bytes[..]);
627
628 Ok(())
629 }
630
631 async fn decomp_basic(
633 encoding: Option<&str>,
634 data: &[u8],
635 maxlen: usize,
636 ) -> (RequestResult<()>, Vec<u8>) {
637 #[allow(deprecated)] let mock_time = SimpleMockTimeProvider::from_wallclock(SystemTime::get());
641
642 let mut output = Vec::new();
643 let mut stream = match get_decoder(data, encoding, AnonymizedRequest::Direct) {
644 Ok(s) => s,
645 Err(e) => return (Err(e), output),
646 };
647
648 let r = read_and_decompress(&mock_time, &mut stream, maxlen, &mut output).await;
649
650 (r, output)
651 }
652
653 #[async_test]
654 async fn decompress_identity() -> RequestResult<()> {
655 let mut text = Vec::new();
656 for _ in 0..1000 {
657 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.");
658 }
659
660 let limit = 10 << 20;
661 let (s, r) = decomp_basic(None, &text[..], limit).await;
662 s?;
663 assert_eq!(r, text);
664
665 let (s, r) = decomp_basic(Some("identity"), &text[..], limit).await;
666 s?;
667 assert_eq!(r, text);
668
669 let limit = 100;
671 let (s, r) = decomp_basic(Some("identity"), &text[..], limit).await;
672 assert!(s.is_err());
673 assert_eq!(r, &text[..100]);
674
675 Ok(())
676 }
677
678 #[async_test]
679 async fn decomp_zlib() -> RequestResult<()> {
680 let compressed =
681 hex::decode("789cf3cf4b5548cb2cce500829cf8730825253200ca79c52881c00e5970c88").unwrap();
682
683 let limit = 10 << 20;
684 let (s, r) = decomp_basic(Some("deflate"), &compressed, limit).await;
685 s?;
686 assert_eq!(r, b"One fish Two fish Red fish Blue fish");
687
688 Ok(())
689 }
690
691 #[cfg(feature = "zstd")]
692 #[async_test]
693 async fn decomp_zstd() -> RequestResult<()> {
694 let compressed = hex::decode("28b52ffd24250d0100c84f6e6520666973682054776f526564426c756520666973680a0200600c0e2509478352cb").unwrap();
695 let limit = 10 << 20;
696 let (s, r) = decomp_basic(Some("x-zstd"), &compressed, limit).await;
697 s?;
698 assert_eq!(r, b"One fish Two fish Red fish Blue fish\n");
699
700 Ok(())
701 }
702
703 #[cfg(feature = "xz")]
704 #[async_test]
705 async fn decomp_xz2() -> RequestResult<()> {
706 let compressed = hex::decode("fd377a585a000004e6d6b446020021011c00000010cf58cce00024001d5d00279b88a202ca8612cfb3c19c87c34248a570451e4851d3323d34ab8000000000000901af64854c91f600013925d6ec06651fb6f37d010000000004595a").unwrap();
708 let limit = 10 << 20;
709 let (s, r) = decomp_basic(Some("x-tor-lzma"), &compressed, limit).await;
710 s?;
711 assert_eq!(r, b"One fish Two fish Red fish Blue fish\n");
712
713 Ok(())
714 }
715
716 #[async_test]
717 async fn decomp_unknown() {
718 let compressed = hex::decode("28b52ffd24250d0100c84f6e6520666973682054776f526564426c756520666973680a0200600c0e2509478352cb").unwrap();
719 let limit = 10 << 20;
720 let (s, _r) = decomp_basic(Some("x-proprietary-rle"), &compressed, limit).await;
721
722 assert!(matches!(s, Err(RequestError::ContentEncoding(_))));
723 }
724
725 #[async_test]
726 async fn decomp_bad_data() {
727 let compressed = b"This is not good zlib data";
728 let limit = 10 << 20;
729 let (s, _r) = decomp_basic(Some("deflate"), compressed, limit).await;
730
731 assert!(matches!(s, Err(RequestError::IoError(_))));
733 }
734
735 #[async_test]
736 async fn headers_ok() -> RequestResult<()> {
737 let text = b"HTTP/1.0 200 OK\r\nDate: ignored\r\nContent-Encoding: Waffles\r\n\r\n";
738
739 let mut s = &text[..];
740 let h = read_headers(&mut s).await?;
741
742 assert_eq!(h.status, Some(200));
743 assert_eq!(h.encoding.as_deref(), Some("Waffles"));
744
745 let mut s = &text[..15];
747 let h = read_headers(&mut s).await;
748 assert!(matches!(h, Err(RequestError::TruncatedHeaders)));
749
750 let text = b"HTTP/1.0 404 Not found\r\n\r\n";
752 let mut s = &text[..];
753 let h = read_headers(&mut s).await?;
754
755 assert_eq!(h.status, Some(404));
756 assert!(h.encoding.is_none());
757
758 Ok(())
759 }
760
761 #[async_test]
762 async fn headers_bogus() -> Result<()> {
763 let text = b"HTTP/999.0 WHAT EVEN\r\n\r\n";
764 let mut s = &text[..];
765 let h = read_headers(&mut s).await;
766
767 assert!(h.is_err());
768 assert!(matches!(h, Err(RequestError::HttparseError(_))));
769 Ok(())
770 }
771
772 fn run_download_test<Req: request::Requestable>(
778 req: Req,
779 response: &[u8],
780 ) -> (Result<DirResponse>, RequestResult<Vec<u8>>) {
781 let (mut s1, s2) = stream_pair();
782 let (mut s2_r, mut s2_w) = s2.split();
783
784 tor_rtcompat::test_with_one_runtime!(|rt| async move {
785 let rt2 = rt.clone();
786 let (v1, v2, v3): (
787 Result<DirResponse>,
788 RequestResult<Vec<u8>>,
789 RequestResult<()>,
790 ) = futures::join!(
791 async {
792 let r = send_request(&rt, &req, &mut s1, None).await;
794 s1.close().await.map_err(|error| {
795 Error::RequestFailed(RequestFailedError {
796 source: None,
797 error: error.into(),
798 })
799 })?;
800 r
801 },
802 async {
803 let mut v = Vec::new();
805 s2_r.read_to_end(&mut v).await?;
806 Ok(v)
807 },
808 async {
809 s2_w.write_all(response).await?;
811 rt2.sleep(Duration::from_millis(50)).await;
824 s2_w.close().await?;
825 Ok(())
826 }
827 );
828
829 assert!(v3.is_ok());
830
831 (v1, v2)
832 })
833 }
834
835 #[test]
836 fn test_send_request() -> RequestResult<()> {
837 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
838
839 let (response, request) = run_download_test(
840 req,
841 b"HTTP/1.0 200 OK\r\n\r\nThis is where the descs would go.",
842 );
843
844 let request = request?;
845 assert!(request[..].starts_with(
846 b"GET /tor/micro/d/CQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQk HTTP/1.0\r\n"
847 ));
848
849 let response = response.unwrap();
850 assert_eq!(response.status_code(), 200);
851 assert!(!response.is_partial());
852 assert!(response.error().is_none());
853 assert!(response.source().is_none());
854 let out_ref = response.output_unchecked();
855 assert_eq!(out_ref, b"This is where the descs would go.");
856 let out = response.into_output_unchecked();
857 assert_eq!(&out, b"This is where the descs would go.");
858
859 Ok(())
860 }
861
862 #[test]
863 fn test_download_truncated() {
864 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
866 let mut response_text: Vec<u8> =
867 (*b"HTTP/1.0 200 OK\r\nContent-Encoding: deflate\r\n\r\n").into();
868 response_text.extend(
870 hex::decode("789cf3cf4b5548cb2cce500829cf8730825253200ca79c52881c00e5970c88").unwrap(),
871 );
872 response_text.extend(
873 hex::decode("789cf3cf4b5548cb2cce500829cf8730825253200ca79c52881c00e5").unwrap(),
874 );
875 let (response, request) = run_download_test(req, &response_text);
876 assert!(request.is_ok());
877 assert!(response.is_err()); let req: request::MicrodescRequest = vec![[9; 32]; 2].into_iter().collect();
881
882 let (response, request) = run_download_test(req, &response_text);
883 assert!(request.is_ok());
884
885 let response = response.unwrap();
886 assert_eq!(response.status_code(), 200);
887 assert!(response.error().is_some());
888 assert!(response.is_partial());
889 assert!(response.output_unchecked().len() < 37 * 2);
890 assert!(response.output_unchecked().starts_with(b"One fish"));
891 }
892
893 #[test]
894 fn test_404() {
895 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
896 let response_text = b"HTTP/1.0 418 I'm a teapot\r\n\r\n";
897 let (response, _request) = run_download_test(req, response_text);
898
899 assert_eq!(response.unwrap().status_code(), 418);
900 }
901
902 #[test]
903 fn test_headers_truncated() {
904 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
905 let response_text = b"HTTP/1.0 404 truncation happens here\r\n";
906 let (response, _request) = run_download_test(req, response_text);
907
908 assert!(matches!(
909 response,
910 Err(Error::RequestFailed(RequestFailedError {
911 error: RequestError::TruncatedHeaders,
912 ..
913 }))
914 ));
915
916 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
918 let response_text = b"";
919 let (response, _request) = run_download_test(req, response_text);
920
921 assert!(matches!(
922 response,
923 Err(Error::RequestFailed(RequestFailedError {
924 error: RequestError::TruncatedHeaders,
925 ..
926 }))
927 ));
928 }
929
930 #[test]
931 fn test_headers_too_long() {
932 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
933 let mut response_text: Vec<u8> = (*b"HTTP/1.0 418 I'm a teapot\r\nX-Too-Many-As: ").into();
934 response_text.resize(16384, b'A');
935 let (response, _request) = run_download_test(req, &response_text);
936
937 assert!(response.as_ref().unwrap_err().should_retire_circ());
938 assert!(matches!(
939 response,
940 Err(Error::RequestFailed(RequestFailedError {
941 error: RequestError::HeadersTooLong(_),
942 ..
943 }))
944 ));
945 }
946
947 }