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#![allow(clippy::cognitive_complexity)] #![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#![deny(clippy::string_slice)] #![cfg_attr(
52 not(all(feature = "full", feature = "experimental")),
53 allow(unused_imports)
54)]
55
56mod body;
57mod err;
58pub mod request;
59mod response;
60mod util;
61
62use tor_circmgr::{CircMgr, DirInfo};
63use tor_error::bad_api_usage;
64use tor_rtcompat::{Runtime, SleepProvider, SleepProviderExt};
65
66#[cfg(feature = "xz")]
68use async_compression::futures::bufread::XzDecoder;
69use async_compression::futures::bufread::ZlibDecoder;
70#[cfg(feature = "zstd")]
71use async_compression::futures::bufread::ZstdDecoder;
72
73use futures::FutureExt;
74use futures::io::{
75 AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader,
76};
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_skip_optimization(&tunnel)
195 .await
196 .map_err(wrap_err)?;
197
198 let mut stream = runtime
200 .timeout(begin_timeout, tunnel.begin_dir_stream())
201 .await
202 .map_err(RequestError::from)
203 .map_err(wrap_err)?
204 .map_err(RequestError::from)
205 .map_err(wrap_err)?; let r = send_request(runtime, req, &mut stream, source.clone()).await;
210
211 if should_retire_circ(&r) {
212 retire_circ(&circ_mgr, &tunnel.unique_id(), "Partial response");
213 }
214
215 r
216}
217
218fn should_retire_circ(result: &Result<DirResponse>) -> bool {
221 match result {
222 Err(e) => e.should_retire_circ(),
223 Ok(dr) => dr.error().map(RequestError::should_retire_circ) == Some(true),
224 }
225}
226
227#[deprecated(since = "0.8.1", note = "Use send_request instead.")]
229pub async fn download<R, S, SP>(
230 runtime: &SP,
231 req: &R,
232 stream: &mut S,
233 source: Option<SourceInfo>,
234) -> Result<DirResponse>
235where
236 R: request::Requestable + ?Sized,
237 S: AsyncRead + AsyncWrite + Send + Unpin,
238 SP: SleepProvider,
239{
240 send_request(runtime, req, stream, source).await
241}
242
243pub async fn send_request<R, S, SP>(
262 runtime: &SP,
263 req: &R,
264 stream: &mut S,
265 source: Option<SourceInfo>,
266) -> Result<DirResponse>
267where
268 R: request::Requestable + ?Sized,
269 S: AsyncRead + AsyncWrite + Send + Unpin,
270 SP: SleepProvider,
271{
272 let wrap_err = |error| {
273 Error::RequestFailed(RequestFailedError {
274 source: source.clone(),
275 error,
276 })
277 };
278
279 let partial_ok = req.partial_response_body_ok();
280 let maxlen = req.max_response_len();
281 let anonymized = req.anonymized();
282 let req = req.make_request().map_err(wrap_err)?;
283 let method = req.method().clone();
284 let encoded = util::encode_request(&req);
285
286 for chunk in encoded.iter() {
288 stream
289 .write_all(chunk)
290 .await
291 .map_err(RequestError::from)
292 .map_err(wrap_err)?;
293 }
294 stream
295 .flush()
296 .await
297 .map_err(RequestError::from)
298 .map_err(wrap_err)?;
299
300 let mut buffered = BufReader::new(stream);
301
302 let header = read_headers(runtime, &mut buffered)
304 .await
305 .map_err(wrap_err)?;
306 if header.status != Some(200) {
307 return Ok(DirResponse::new(
308 method,
309 header.status.unwrap_or(0),
310 header.status_message,
311 None,
312 vec![],
313 source,
314 ));
315 }
316
317 let mut decoder =
318 get_decoder(buffered, header.encoding.as_deref(), anonymized).map_err(wrap_err)?;
319
320 let mut result = Vec::new();
321 let ok = read_and_decompress(runtime, &mut decoder, maxlen, &mut result).await;
322
323 let ok = match (partial_ok, ok, result.len()) {
324 (true, Err(e), n) if n > 0 => {
325 Err(e)
327 }
328 (_, Err(e), _) => {
329 return Err(wrap_err(e));
330 }
331 (_, Ok(()), _) => Ok(()),
332 };
333
334 Ok(DirResponse::new(
335 method,
336 200,
337 None,
338 ok.err(),
339 result,
340 source,
341 ))
342}
343
344const MAX_HEADERS_LEN: usize = 16384;
348
349async fn read_headers<S, SP>(runtime: &SP, stream: &mut S) -> RequestResult<HeaderStatus>
353where
354 S: AsyncBufRead + Unpin,
355 SP: SleepProvider,
356{
357 let read_timeout = Duration::from_secs(10);
360 runtime
361 .timeout(read_timeout, read_headers_internal(stream))
362 .await
363 .map_err(|_| RequestError::DirTimeout)
364 .flatten()
365}
366
367async fn read_headers_internal<S>(stream: &mut S) -> RequestResult<HeaderStatus>
369where
370 S: AsyncBufRead + Unpin,
371{
372 let mut buf = Vec::with_capacity(1024);
373
374 let mut limited_stream = stream.take(MAX_HEADERS_LEN as u64);
377
378 loop {
379 let n = limited_stream.read_until(b'\n', &mut buf).await?;
383
384 let mut headers = [httparse::EMPTY_HEADER; 32];
386 let mut response = httparse::Response::new(&mut headers);
387
388 match response.parse(&buf[..])? {
389 httparse::Status::Partial => {
390 if n == 0 {
393 return Err(RequestError::TruncatedHeaders);
395 }
396
397 if buf.len() >= MAX_HEADERS_LEN {
398 return Err(RequestError::HeadersTooLong(buf.len()));
399 }
400 }
401 httparse::Status::Complete(n_parsed) => {
402 if response.code != Some(200) {
403 return Ok(HeaderStatus {
404 status: response.code,
405 status_message: response.reason.map(str::to_owned),
406 encoding: None,
407 });
408 }
409 let encoding = if let Some(enc) = response
410 .headers
411 .iter()
412 .find(|h| h.name == "Content-Encoding")
413 {
414 Some(String::from_utf8(enc.value.to_vec())?)
415 } else {
416 None
417 };
418 assert!(n_parsed == buf.len());
425 return Ok(HeaderStatus {
426 status: Some(200),
427 status_message: None,
428 encoding,
429 });
430 }
431 }
432 if n == 0 {
433 return Err(RequestError::TruncatedHeaders);
434 }
435 }
436}
437
438#[derive(Debug, Clone)]
440struct HeaderStatus {
441 status: Option<u16>,
443 status_message: Option<String>,
445 encoding: Option<String>,
447}
448
449async fn read_and_decompress<S, SP>(
458 runtime: &SP,
459 mut stream: S,
460 maxlen: usize,
461 result: &mut Vec<u8>,
462) -> RequestResult<()>
463where
464 S: AsyncRead + Unpin,
465 SP: SleepProvider,
466{
467 let buffer_window_size = 1024;
468 let mut written_total: usize = 0;
469 let read_timeout = Duration::from_secs(10);
472 let timer = runtime.sleep(read_timeout).fuse();
473 futures::pin_mut!(timer);
474
475 loop {
476 result.resize(written_total + buffer_window_size, 0);
478 let buf: &mut [u8] = &mut result[written_total..written_total + buffer_window_size];
479
480 let status = futures::select! {
481 status = stream.read(buf).fuse() => status,
482 _ = timer => {
483 result.resize(written_total, 0); return Err(RequestError::DirTimeout);
485 }
486 };
487 let written_in_this_loop = match status {
488 Ok(n) => n,
489 Err(other) => {
490 result.resize(written_total, 0); return Err(other.into());
492 }
493 };
494
495 written_total += written_in_this_loop;
496
497 if written_in_this_loop == 0 {
500 if written_total < result.len() {
506 result.resize(written_total, 0);
507 }
508 return Ok(());
509 }
510
511 if written_total > maxlen {
517 result.resize(maxlen, 0);
518 return Err(RequestError::ResponseTooLong(written_total));
519 }
520 }
521}
522
523fn retire_circ<R>(circ_mgr: &Arc<CircMgr<R>>, id: &tor_proto::circuit::UniqId, error: &str)
525where
526 R: Runtime,
527{
528 info!(
529 "{}: Retiring circuit because of directory failure: {}",
530 &id, &error
531 );
532 circ_mgr.retire_circ(id);
533}
534
535const LZMA_DICT_MEM_LIMIT: u64 = 1 << 24; macro_rules! decoder {
561 (XzDecoder, $s:expr) => {{
562 let mut decoder = XzDecoder::with_mem_limit($s, LZMA_DICT_MEM_LIMIT);
563 decoder.multiple_members(true);
564 Ok(Box::new(decoder))
565 }};
566 ($dec:ident, $s:expr) => {{
567 let mut decoder = $dec::new($s);
568 decoder.multiple_members(true);
569 Ok(Box::new(decoder))
570 }};
571}
572
573fn get_decoder<'a, S: AsyncBufRead + Unpin + Send + 'a>(
576 stream: S,
577 encoding: Option<&str>,
578 anonymized: AnonymizedRequest,
579) -> RequestResult<Box<dyn AsyncRead + Unpin + Send + 'a>> {
580 use AnonymizedRequest::Direct;
581 match (encoding, anonymized) {
582 (None | Some("identity"), _) => Ok(Box::new(stream)),
583 (Some("deflate"), _) => decoder!(ZlibDecoder, stream),
584 #[cfg(feature = "xz")]
588 (Some("x-tor-lzma"), Direct) => decoder!(XzDecoder, stream),
589 #[cfg(feature = "zstd")]
590 (Some("x-zstd"), Direct) => decoder!(ZstdDecoder, stream),
591 (Some(other), _) => Err(RequestError::ContentEncoding(other.into())),
592 }
593}
594
595#[cfg(test)]
596mod test {
597 #![allow(clippy::bool_assert_comparison)]
599 #![allow(clippy::clone_on_copy)]
600 #![allow(clippy::dbg_macro)]
601 #![allow(clippy::mixed_attributes_style)]
602 #![allow(clippy::print_stderr)]
603 #![allow(clippy::print_stdout)]
604 #![allow(clippy::single_char_pattern)]
605 #![allow(clippy::unwrap_used)]
606 #![allow(clippy::unchecked_time_subtraction)]
607 #![allow(clippy::useless_vec)]
608 #![allow(clippy::needless_pass_by_value)]
609 #![allow(clippy::string_slice)] use super::*;
612 use tor_rtmock::io::stream_pair;
613
614 use tor_rtmock::simple_time::SimpleMockTimeProvider;
615 use web_time_compat::{SystemTime, SystemTimeExt};
616
617 use futures_await_test::async_test;
618
619 async fn decomp_basic(
621 encoding: Option<&str>,
622 data: &[u8],
623 maxlen: usize,
624 ) -> (RequestResult<()>, Vec<u8>) {
625 #[allow(deprecated)] let mock_time = SimpleMockTimeProvider::from_wallclock(SystemTime::get());
629
630 let mut output = Vec::new();
631 let mut stream = match get_decoder(data, encoding, AnonymizedRequest::Direct) {
632 Ok(s) => s,
633 Err(e) => return (Err(e), output),
634 };
635
636 let r = read_and_decompress(&mock_time, &mut stream, maxlen, &mut output).await;
637
638 (r, output)
639 }
640
641 #[async_test]
642 async fn decompress_identity() -> RequestResult<()> {
643 let mut text = Vec::new();
644 for _ in 0..1000 {
645 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.");
646 }
647
648 let limit = 10 << 20;
649 let (s, r) = decomp_basic(None, &text[..], limit).await;
650 s?;
651 assert_eq!(r, text);
652
653 let (s, r) = decomp_basic(Some("identity"), &text[..], limit).await;
654 s?;
655 assert_eq!(r, text);
656
657 let limit = 100;
659 let (s, r) = decomp_basic(Some("identity"), &text[..], limit).await;
660 assert!(s.is_err());
661 assert_eq!(r, &text[..100]);
662
663 Ok(())
664 }
665
666 #[async_test]
667 async fn decomp_zlib() -> RequestResult<()> {
668 let compressed =
669 hex::decode("789cf3cf4b5548cb2cce500829cf8730825253200ca79c52881c00e5970c88").unwrap();
670
671 let limit = 10 << 20;
672 let (s, r) = decomp_basic(Some("deflate"), &compressed, limit).await;
673 s?;
674 assert_eq!(r, b"One fish Two fish Red fish Blue fish");
675
676 Ok(())
677 }
678
679 #[cfg(feature = "zstd")]
680 #[async_test]
681 async fn decomp_zstd() -> RequestResult<()> {
682 let compressed = hex::decode("28b52ffd24250d0100c84f6e6520666973682054776f526564426c756520666973680a0200600c0e2509478352cb").unwrap();
683 let limit = 10 << 20;
684 let (s, r) = decomp_basic(Some("x-zstd"), &compressed, limit).await;
685 s?;
686 assert_eq!(r, b"One fish Two fish Red fish Blue fish\n");
687
688 Ok(())
689 }
690
691 #[cfg(feature = "xz")]
692 #[async_test]
693 async fn decomp_xz() -> RequestResult<()> {
694 let compressed = hex::decode("fd377a585a000004e6d6b44604c02525210116000000000000000000bfdbca33e00024001d5d00279b88a202ca8612cfb3c19c87c34248a570451e4851d3323d34ab8000000000000901af64854c91f600014125281fe0821fb6f37d010000000004595a").unwrap();
696 let limit = LZMA_DICT_MEM_LIMIT as usize;
697 let (s, r) = decomp_basic(Some("x-tor-lzma"), &compressed, limit).await;
698 s?;
699 assert_eq!(r, b"One fish Two fish Red fish Blue fish\n");
700
701 Ok(())
702 }
703
704 #[cfg(feature = "xz")]
705 #[async_test]
706 async fn decomp_xz_bad() -> RequestResult<()> {
707 let compressed = hex::decode("fd377a585a000004e6d6b446020021011c00000010cf58cce00024001d5d00279b88a202ca8612cfb3c19c87c34248a570451e4851d3323d34ab8000000000000901af64854c91f600013925d6ec06651fb6f37d010000000004595a").unwrap();
709 let limit = 10 << 20;
710 let (s, _) = decomp_basic(Some("x-tor-lzma"), &compressed, limit).await;
711 assert!(matches!(s, Err(RequestError::IoError(_))));
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 #[test]
736 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 = tor_rtcompat::test_with_one_runtime!(async |rt| read_headers(&rt, &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 = tor_rtcompat::test_with_one_runtime!(async |rt| read_headers(&rt, &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 = tor_rtcompat::test_with_one_runtime!(async |rt| read_headers(&rt, &mut s).await)?;
754
755 assert_eq!(h.status, Some(404));
756 assert!(h.encoding.is_none());
757
758 Ok(())
759 }
760
761 #[test]
762 fn headers_bogus() {
763 let text = b"HTTP/999.0 WHAT EVEN\r\n\r\n";
764 let mut s = &text[..];
765 let h = tor_rtcompat::test_with_one_runtime!(async |rt| read_headers(&rt, &mut s).await);
766
767 assert!(h.is_err());
768 assert!(matches!(h, Err(RequestError::HttparseError(_))));
769 }
770
771 fn run_download_test<Req: request::Requestable>(
777 req: Req,
778 response: &[u8],
779 ) -> (Result<DirResponse>, RequestResult<Vec<u8>>) {
780 let (mut s1, s2) = stream_pair();
781 let (mut s2_r, mut s2_w) = s2.split();
782
783 tor_rtcompat::test_with_one_runtime!(|rt| async move {
784 let rt2 = rt.clone();
785 let (v1, v2, v3): (
786 Result<DirResponse>,
787 RequestResult<Vec<u8>>,
788 RequestResult<()>,
789 ) = futures::join!(
790 async {
791 let r = send_request(&rt, &req, &mut s1, None).await;
793 s1.close().await.map_err(|error| {
794 Error::RequestFailed(RequestFailedError {
795 source: None,
796 error: error.into(),
797 })
798 })?;
799 r
800 },
801 async {
802 let mut v = Vec::new();
804 s2_r.read_to_end(&mut v).await?;
805 Ok(v)
806 },
807 async {
808 s2_w.write_all(response).await?;
810 rt2.sleep(Duration::from_millis(50)).await;
823 s2_w.close().await?;
824 Ok(())
825 }
826 );
827
828 assert!(v3.is_ok());
829
830 (v1, v2)
831 })
832 }
833
834 #[test]
835 fn test_send_request() -> RequestResult<()> {
836 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
837
838 let (response, request) = run_download_test(
839 req,
840 b"HTTP/1.0 200 OK\r\n\r\nThis is where the descs would go.",
841 );
842
843 let request = request?;
844 assert!(request[..].starts_with(
845 b"GET /tor/micro/d/CQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQk HTTP/1.0\r\n"
846 ));
847
848 let response = response.unwrap();
849 assert_eq!(response.status_code(), 200);
850 assert!(!response.is_partial());
851 assert!(response.error().is_none());
852 assert!(response.source().is_none());
853 let out_ref = response.output_unchecked();
854 assert_eq!(out_ref, b"This is where the descs would go.");
855 let out = response.into_output_unchecked();
856 assert_eq!(&out, b"This is where the descs would go.");
857
858 Ok(())
859 }
860
861 #[test]
862 fn test_download_truncated() {
863 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
865 let mut response_text: Vec<u8> =
866 (*b"HTTP/1.0 200 OK\r\nContent-Encoding: deflate\r\n\r\n").into();
867 response_text.extend(
869 hex::decode("789cf3cf4b5548cb2cce500829cf8730825253200ca79c52881c00e5970c88").unwrap(),
870 );
871 response_text.extend(
872 hex::decode("789cf3cf4b5548cb2cce500829cf8730825253200ca79c52881c00e5").unwrap(),
873 );
874 let (response, request) = run_download_test(req, &response_text);
875 assert!(request.is_ok());
876 assert!(response.is_err()); let req: request::MicrodescRequest = vec![[9; 32]; 2].into_iter().collect();
880
881 let (response, request) = run_download_test(req, &response_text);
882 assert!(request.is_ok());
883
884 let response = response.unwrap();
885 assert_eq!(response.status_code(), 200);
886 assert!(response.error().is_some());
887 assert!(response.is_partial());
888 assert!(response.output_unchecked().len() < 37 * 2);
889 assert!(response.output_unchecked().starts_with(b"One fish"));
890 }
891
892 #[test]
893 fn test_404() {
894 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
895 let response_text = b"HTTP/1.0 418 I'm a teapot\r\n\r\n";
896 let (response, _request) = run_download_test(req, response_text);
897
898 assert_eq!(response.unwrap().status_code(), 418);
899 }
900
901 #[test]
902 fn test_headers_truncated() {
903 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
904 let response_text = b"HTTP/1.0 404 truncation happens here\r\n";
905 let (response, _request) = run_download_test(req, response_text);
906
907 assert!(matches!(
908 response,
909 Err(Error::RequestFailed(RequestFailedError {
910 error: RequestError::TruncatedHeaders,
911 ..
912 }))
913 ));
914
915 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
917 let response_text = b"";
918 let (response, _request) = run_download_test(req, response_text);
919
920 assert!(matches!(
921 response,
922 Err(Error::RequestFailed(RequestFailedError {
923 error: RequestError::TruncatedHeaders,
924 ..
925 }))
926 ));
927 }
928
929 #[test]
930 fn test_headers_too_long() {
931 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
932 let mut response_text: Vec<u8> = (*b"HTTP/1.0 418 I'm a teapot\r\nX-Too-Many-As: ").into();
933 response_text.resize(16384, b'A');
934 let (response, _request) = run_download_test(req, &response_text);
935
936 assert!(response.as_ref().unwrap_err().should_retire_circ());
937 assert!(matches!(
938 response,
939 Err(Error::RequestFailed(RequestFailedError {
940 error: RequestError::HeadersTooLong(_),
941 ..
942 }))
943 ));
944 }
945
946 #[test]
947 fn test_bad_utf8() {
948 let req: request::MicrodescRequest = vec![[9; 32]].into_iter().collect();
949 let faulty_utf8 = vec![0, 159, 146, 150];
950
951 let mut response_text: Vec<u8> = b"HTTP/1.0 200 OK\r\n\r\n".into();
952 response_text.extend(faulty_utf8);
953
954 let (response, _request) = run_download_test(req, &response_text);
955
956 assert!(matches!(
957 response.unwrap().into_output_string().unwrap_err(),
958 RequestFailedError {
959 error: RequestError::Utf8Encoding(_),
960 ..
961 }
962 ));
963 }
964}