1use std::{
2 error::Error,
3 io::{BufReader, Read},
4 net::{IpAddr, SocketAddr},
5 sync::{
6 Arc,
7 atomic::{AtomicBool, Ordering},
8 },
9 time::Duration,
10};
11
12use bytes::Bytes;
13use reqwest::{
14 Client, StatusCode, Url,
15 dns::{Addrs, Name, Resolve, Resolving},
16 header::{ACCEPT_ENCODING, CONTENT_ENCODING, HeaderMap, LOCATION},
17 redirect::Policy,
18};
19use tokio::sync::mpsc;
20use tokio::time::timeout;
21
22use super::{
23 CONNECT_TIMEOUT, IDLE_TIMEOUT, PutOutcome, UploadRequest, checked_count, is_loopback_name,
24};
25use crate::{
26 AttachmentError, AttachmentFailureCause as Cause,
27 address::is_private,
28 store::{AttachmentOptions, CHUNK_SIZE, DownloadSink, StagedFile},
29};
30
31mod upload;
32
33const DEFLATE_OVERHEAD_DIVISOR: u64 = 1000;
34
35fn compressed_input_cap(decoded_cap: u64) -> u64 {
36 let decoded_cap = decoded_cap.min(u32::MAX as u64);
39 decoded_cap
40 .saturating_add(decoded_cap / DEFLATE_OVERHEAD_DIVISOR)
41 .saturating_add(CHUNK_SIZE as u64)
42}
43
44#[derive(Debug)]
45struct BlockedDns;
46
47impl std::fmt::Display for BlockedDns {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.write_str("all DNS answers are blocked attachment addresses")
50 }
51}
52
53impl Error for BlockedDns {}
54
55struct SystemResolver;
56
57impl Resolve for SystemResolver {
58 fn resolve(&self, name: Name) -> Resolving {
59 let host = name.as_str().to_owned();
60 Box::pin(async move {
61 let addrs: Vec<_> = tokio::net::lookup_host((host.as_str(), 0)).await?.collect();
62 Ok(Box::new(addrs.into_iter()) as Addrs)
63 })
64 }
65}
66
67struct GuardedResolver {
68 upstream: Arc<dyn Resolve>,
69 allow_private: bool,
70}
71
72impl Resolve for GuardedResolver {
73 fn resolve(&self, name: Name) -> Resolving {
74 let answer = if is_loopback_name(name.as_str()) {
75 let addrs = [
76 SocketAddr::new(std::net::Ipv4Addr::LOCALHOST.into(), 0),
77 SocketAddr::new(std::net::Ipv6Addr::LOCALHOST.into(), 0),
78 ];
79 Box::pin(async move { Ok(Box::new(addrs.into_iter()) as Addrs) }) as Resolving
80 } else {
81 self.upstream.resolve(name)
82 };
83 let allow_private = self.allow_private;
84 Box::pin(async move {
85 let addrs: Vec<SocketAddr> = answer
86 .await?
87 .filter(|addr| allow_private || !is_private(addr.ip()))
88 .collect();
89 if addrs.is_empty() {
90 return Err(Box::new(BlockedDns) as Box<dyn Error + Send + Sync>);
91 }
92 Ok(Box::new(addrs.into_iter()) as Addrs)
93 })
94 }
95}
96
97fn reqwest_error(error: reqwest::Error) -> AttachmentError {
98 let mut source: Option<&(dyn Error + 'static)> = Some(&error);
99 while let Some(current) = source {
100 if current.is::<BlockedDns>() {
101 return AttachmentError::new(Cause::BlockedAddress);
102 }
103 source = current.source();
104 }
105 AttachmentError::new(Cause::Network)
106}
107
108fn validate_url(url: &Url, options: &AttachmentOptions) -> Result<(), AttachmentError> {
109 let host = url.host().ok_or(AttachmentError::new(Cause::InsecureUrl))?;
110 let loopback = match host {
111 url::Host::Domain(name) => is_loopback_name(name),
112 url::Host::Ipv4(address) => address.is_loopback(),
113 url::Host::Ipv6(address) => address.is_loopback(),
114 };
115 if url.scheme() != "https"
116 && !(url.scheme() == "http" && loopback && options.allow_private_network)
117 {
118 return Err(AttachmentError::new(Cause::InsecureUrl));
119 }
120 let ip = match host {
121 url::Host::Ipv4(ip) => Some(IpAddr::V4(ip)),
122 url::Host::Ipv6(ip) => Some(IpAddr::V6(ip)),
123 url::Host::Domain(_) => None,
124 };
125 if ip.is_some_and(|address| !options.allow_private_network && is_private(address)) {
126 return Err(AttachmentError::new(Cause::BlockedAddress));
127 }
128 Ok(())
129}
130
131fn content_encoding(
132 headers: &HeaderMap,
133 status: u16,
134) -> Result<Option<&'static str>, AttachmentError> {
135 let mut coding = None;
136 for value in headers.get_all(CONTENT_ENCODING) {
137 let value = value
138 .to_str()
139 .map_err(|_| AttachmentError::with_http_status(Cause::HttpStatus, status))?;
140 for part in value.split(',') {
141 let part = part.trim().to_ascii_lowercase();
142 if part == "identity" {
143 continue;
144 }
145 if coding.is_some() {
146 return Err(AttachmentError::with_http_status(Cause::HttpStatus, status));
147 }
148 coding = Some(match part.as_str() {
149 "gzip" | "x-gzip" => "gzip",
150 "deflate" => "deflate",
151 _ => return Err(AttachmentError::with_http_status(Cause::HttpStatus, status)),
152 });
153 }
154 }
155 Ok(coding)
156}
157
158struct AbortOnDrop(tokio::task::AbortHandle);
159
160impl AbortOnDrop {
161 fn new<T>(task: &tokio::task::JoinHandle<T>) -> Self {
162 Self(task.abort_handle())
163 }
164}
165
166impl Drop for AbortOnDrop {
167 fn drop(&mut self) {
168 self.0.abort();
169 }
170}
171
172fn redirect_target(
173 current: &Url,
174 location: &str,
175 redirects: u8,
176 options: &AttachmentOptions,
177) -> Result<Url, AttachmentError> {
178 if redirects == 10 {
179 return Err(AttachmentError::new(Cause::TooManyRedirects));
180 }
181 let target = current
182 .join(location)
183 .map_err(|_| AttachmentError::new(Cause::InsecureUrl))?;
184 validate_url(&target, options)?;
185 Ok(target)
186}
187
188pub struct Transfer {
190 client: Client,
191 upload_resolver: Arc<dyn Resolve>,
192 #[cfg(test)]
193 upload_test_roots: Option<rustls::RootCertStore>,
194 #[cfg(test)]
195 upload_socket_failure: Option<upload::SocketSetupFailure>,
196 options: AttachmentOptions,
197 connect_timeout: Duration,
198 idle_timeout: Duration,
199}
200
201impl Transfer {
202 pub fn new(options: AttachmentOptions) -> Result<Self, AttachmentError> {
203 Self::with_resolver(options, Arc::new(SystemResolver))
204 }
205
206 fn with_resolver(
207 options: AttachmentOptions,
208 upstream: Arc<dyn Resolve>,
209 ) -> Result<Self, AttachmentError> {
210 Self::with_resolver_and_timeouts(options, upstream, CONNECT_TIMEOUT, IDLE_TIMEOUT)
211 }
212
213 fn with_resolver_and_timeouts(
214 options: AttachmentOptions,
215 upstream: Arc<dyn Resolve>,
216 connect_timeout: Duration,
217 idle_timeout: Duration,
218 ) -> Result<Self, AttachmentError> {
219 let resolver = Arc::new(GuardedResolver {
220 upstream: upstream.clone(),
221 allow_private: options.allow_private_network,
222 });
223 let client = xmtp_common::http::client_builder()
224 .no_proxy()
225 .dns_resolver(resolver.clone())
226 .redirect(Policy::none())
227 .connect_timeout(connect_timeout)
228 .build()
229 .map_err(reqwest_error)?;
230 Ok(Self {
231 client,
232 upload_resolver: upstream,
233 #[cfg(test)]
234 upload_test_roots: None,
235 #[cfg(test)]
236 upload_socket_failure: None,
237 options,
238 connect_timeout,
239 idle_timeout,
240 })
241 }
242
243 #[cfg(test)]
244 fn with_timeouts(
245 options: AttachmentOptions,
246 connect: Duration,
247 idle: Duration,
248 ) -> Result<Self, AttachmentError> {
249 Self::with_resolver_and_timeouts(options, Arc::new(SystemResolver), connect, idle)
250 }
251
252 pub async fn put(
253 &self,
254 request: &UploadRequest,
255 body: StagedFile,
256 ) -> Result<PutOutcome, AttachmentError> {
257 upload::put(self, request, body).await
258 }
259
260 pub async fn get(
261 &self,
262 url: &str,
263 cap: u64,
264 sink: &mut dyn DownloadSink,
265 ) -> Result<(), AttachmentError> {
266 let mut url = Url::parse(url).map_err(|_| AttachmentError::new(Cause::InsecureUrl))?;
267 let mut redirects = 0;
268 let response = loop {
269 validate_url(&url, &self.options)?;
270 let response = timeout(
271 self.idle_timeout,
272 self.client
273 .get(url.clone())
274 .header(ACCEPT_ENCODING, "identity")
275 .send(),
276 )
277 .await
278 .map_err(|_| AttachmentError::new(Cause::Network))?
279 .map_err(reqwest_error)?;
280 if !matches!(
281 response.status(),
282 StatusCode::MOVED_PERMANENTLY
283 | StatusCode::FOUND
284 | StatusCode::SEE_OTHER
285 | StatusCode::TEMPORARY_REDIRECT
286 | StatusCode::PERMANENT_REDIRECT
287 ) {
288 break response;
289 }
290 let status = response.status().as_u16();
291 let location = response
292 .headers()
293 .get(LOCATION)
294 .ok_or(AttachmentError::with_http_status(Cause::HttpStatus, status))?
295 .to_str()
296 .map_err(|_| AttachmentError::with_http_status(Cause::HttpStatus, status))?;
297 url = redirect_target(&url, location, redirects, &self.options)?;
298 redirects += 1;
299 };
300 let status = response.status().as_u16();
301 match response.status() {
302 StatusCode::OK => {}
303 StatusCode::NOT_FOUND | StatusCode::GONE => {
304 return Err(AttachmentError::new(Cause::NotFound));
305 }
306 _ => return Err(AttachmentError::with_http_status(Cause::HttpStatus, status)),
307 }
308 let encoding = content_encoding(response.headers(), status)?;
309 let cap = cap.min(self.options.max_download_bytes.unwrap_or(u64::MAX));
310 match encoding {
311 None => read_identity(response, cap, sink, self.idle_timeout).await,
312 Some(encoding) => {
313 read_compressed(response, encoding, cap, sink, self.idle_timeout).await
314 }
315 }
316 }
317}
318
319async fn read_identity(
320 mut response: reqwest::Response,
321 cap: u64,
322 sink: &mut dyn DownloadSink,
323 idle_timeout: Duration,
324) -> Result<(), AttachmentError> {
325 let mut count = 0;
326 while let Some(chunk) = timeout(idle_timeout, response.chunk())
327 .await
328 .map_err(|_| AttachmentError::new(Cause::Network))?
329 .map_err(reqwest_error)?
330 {
331 count = checked_count(count, chunk.len(), cap)?;
332 sink.write(&chunk).await?;
333 }
334 Ok(())
335}
336
337struct ChannelReader {
338 rx: mpsc::Receiver<Bytes>,
339 consumed: mpsc::Sender<()>,
340 chunk: Bytes,
341 position: usize,
342}
343
344impl Read for ChannelReader {
345 fn read(&mut self, output: &mut [u8]) -> std::io::Result<usize> {
346 if self.position == self.chunk.len() {
347 if !self.chunk.is_empty() {
348 self.chunk = Bytes::new();
349 self.position = 0;
350 if self.consumed.blocking_send(()).is_err() {
351 return Ok(0);
352 }
353 }
354 let Some(bytes) = self.rx.blocking_recv() else {
355 return Ok(0);
356 };
357 self.chunk = bytes;
358 self.position = 0;
359 }
360 let count = output.len().min(self.chunk.len() - self.position);
361 output[..count].copy_from_slice(&self.chunk[self.position..self.position + count]);
362 self.position += count;
363 Ok(count)
364 }
365}
366
367enum CompressedReader {
368 Gzip(flate2::read::MultiGzDecoder<ChannelReader>),
369 Deflate(flate2::bufread::ZlibDecoder<BufReader<ChannelReader>>),
370}
371
372impl Read for CompressedReader {
373 fn read(&mut self, output: &mut [u8]) -> std::io::Result<usize> {
374 match self {
375 Self::Gzip(reader) => reader.read(output),
376 Self::Deflate(reader) => reader.read(output),
377 }
378 }
379}
380
381impl CompressedReader {
382 fn finish(self) -> Result<(), ()> {
383 if let Self::Deflate(reader) = self {
384 let mut encoded = reader.into_inner();
386 let mut trailing = [0_u8; 1];
387 if encoded.read(&mut trailing).map_err(|_| ())? != 0 {
388 return Err(());
389 }
390 }
391 Ok(())
392 }
393}
394
395async fn read_compressed(
396 mut response: reqwest::Response,
397 encoding: &str,
398 cap: u64,
399 sink: &mut dyn DownloadSink,
400 idle_timeout: Duration,
401) -> Result<(), AttachmentError> {
402 let status = response.status().as_u16();
403 let (input_tx, input_rx) = mpsc::channel(1);
404 let (consumed_tx, mut consumed_rx) = mpsc::channel(1);
405 let (output_tx, mut output_rx) = mpsc::channel(1);
406 let (recycle_tx, mut recycle_rx) = mpsc::channel(1);
407 let network_failed = Arc::new(AtomicBool::new(false));
408 let producer_failed = network_failed.clone();
409 let input_too_large = Arc::new(AtomicBool::new(false));
410 let producer_too_large = input_too_large.clone();
411 let input_cap = compressed_input_cap(cap);
412 let gzip = encoding == "gzip";
413 let producer = tokio::spawn(async move {
414 let mut input_count = 0_u64;
415 let mut decoder_done = false;
416 loop {
417 let chunk = match timeout(idle_timeout, response.chunk()).await {
418 Ok(Ok(Some(chunk))) => chunk,
419 Ok(Ok(None)) => break,
420 Err(_) | Ok(Err(_)) => {
421 producer_failed.store(true, Ordering::Release);
422 break;
423 }
424 };
425 input_count = match input_count.checked_add(chunk.len() as u64) {
426 Some(next) if next <= input_cap => next,
427 _ => {
428 producer_too_large.store(true, Ordering::Release);
429 break;
430 }
431 };
432 if decoder_done {
433 continue;
434 }
435 for start in (0..chunk.len()).step_by(CHUNK_SIZE) {
436 let end = (start + CHUNK_SIZE).min(chunk.len());
437 if input_tx.send(chunk.slice(start..end)).await.is_err() {
438 decoder_done = true;
439 break;
440 }
441 if consumed_rx.recv().await.is_none() {
442 decoder_done = true;
443 break;
444 }
445 }
446 }
447 });
448 let _producer_guard = AbortOnDrop::new(&producer);
451 let decoder = tokio::task::spawn_blocking(move || {
452 let reader = ChannelReader {
453 rx: input_rx,
454 consumed: consumed_tx,
455 chunk: Bytes::new(),
456 position: 0,
457 };
458 let mut reader = if gzip {
459 CompressedReader::Gzip(flate2::read::MultiGzDecoder::new(reader))
460 } else {
461 CompressedReader::Deflate(flate2::bufread::ZlibDecoder::new(BufReader::new(reader)))
462 };
463 let mut output = vec![0_u8; CHUNK_SIZE];
464 loop {
465 let count = reader.read(&mut output).map_err(|_| ())?;
466 if count == 0 {
467 return reader.finish();
468 }
469 if output_tx.blocking_send((output, count)).is_err() {
470 return Ok(());
471 }
472 let Some(recycled) = recycle_rx.blocking_recv() else {
473 return Ok(());
474 };
475 output = recycled;
476 }
477 });
478 let mut count = 0;
479 let mut result = Ok(());
480 while let Some((output, size)) = output_rx.recv().await {
481 match checked_count(count, size, cap) {
482 Ok(next) => count = next,
483 Err(error) => {
484 result = Err(error);
485 break;
486 }
487 }
488 if let Err(error) = sink.write(&output[..size]).await {
489 result = Err(error);
490 break;
491 }
492 if recycle_tx.send(output).await.is_err() {
493 break;
494 }
495 }
496 drop(recycle_tx);
497 drop(output_rx);
498 if result.is_ok() {
499 let decoded = decoder.await;
500 if decoded.as_ref().is_ok_and(|result| result.is_ok()) {
501 let _ = producer.await;
502 } else {
503 producer.abort();
504 }
505 if input_too_large.load(Ordering::Acquire) {
506 return Err(AttachmentError::new(Cause::TooLarge));
507 }
508 if network_failed.load(Ordering::Acquire) {
509 return Err(AttachmentError::new(Cause::Network));
510 }
511 result = decoded
512 .map_err(|_| AttachmentError::with_http_status(Cause::HttpStatus, status))
513 .and_then(|result| {
514 result.map_err(|_| AttachmentError::with_http_status(Cause::HttpStatus, status))
515 });
516 } else {
517 producer.abort();
518 let _ = decoder.await;
519 }
520 result
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526 use crate::store::{LocalStore, NativeStore};
527 use bytes::Bytes;
528 use http_body_util::Full;
529 use hyper::{Request, Response, body::Incoming, server::conn::http1, service::service_fn};
530 use hyper_util::rt::TokioIo;
531 use reqwest::Method;
532 use std::{
533 future::Future,
534 sync::atomic::{AtomicBool, AtomicUsize, Ordering},
535 };
536 use tokio::time::Instant;
537
538 struct Server {
539 url: String,
540 task: tokio::task::JoinHandle<()>,
541 }
542
543 impl Drop for Server {
544 fn drop(&mut self) {
545 self.task.abort();
546 }
547 }
548
549 async fn server<F>(handler: F) -> Server
550 where
551 F: Fn(Request<Incoming>) -> Response<Full<Bytes>> + Send + Sync + 'static,
552 {
553 server_async(move |request| std::future::ready(handler(request))).await
554 }
555
556 async fn server_async<F, Fut>(handler: F) -> Server
557 where
558 F: Fn(Request<Incoming>) -> Fut + Send + Sync + 'static,
559 Fut: Future<Output = Response<Full<Bytes>>> + Send + 'static,
560 {
561 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
562 let port = listener.local_addr().unwrap().port();
563 let handler = Arc::new(handler);
564 let task = tokio::spawn(async move {
565 while let Ok((stream, _)) = listener.accept().await {
566 let handler = handler.clone();
567 tokio::spawn(async move {
568 let service = service_fn(move |request| {
569 let response = handler(request);
570 async move { Ok::<_, std::convert::Infallible>(response.await) }
571 });
572 let _ = http1::Builder::new()
573 .serve_connection(TokioIo::new(stream), service)
574 .await;
575 });
576 }
577 });
578 Server {
579 url: format!("http://127.0.0.1:{port}"),
580 task,
581 }
582 }
583
584 fn answer(status: StatusCode, body: impl Into<Bytes>) -> Response<Full<Bytes>> {
585 Response::builder()
586 .status(status)
587 .body(Full::new(body.into()))
588 .unwrap()
589 }
590
591 fn allowed() -> Transfer {
592 Transfer::new(AttachmentOptions {
593 allow_private_network: true,
594 ..Default::default()
595 })
596 .unwrap()
597 }
598
599 #[derive(Default)]
600 struct MemorySink(Vec<u8>);
601
602 #[async_trait::async_trait]
603 impl DownloadSink for MemorySink {
604 async fn write(&mut self, bytes: &[u8]) -> Result<(), AttachmentError> {
605 self.0.extend_from_slice(bytes);
606 Ok(())
607 }
608 }
609
610 #[xmtp_common::test(unwrap_try = true)]
612 async fn rejects_http_public() {
613 let error = allowed()
614 .get("http://example.com/object", 100, &mut MemorySink::default())
615 .await
616 .unwrap_err();
617 assert_eq!(error.cause, Cause::InsecureUrl);
618 }
619
620 #[xmtp_common::test(unwrap_try = true)]
622 async fn allows_http_loopback_when_permitted() {
623 let server = server(|_| answer(StatusCode::OK, "hello")).await;
624 let mut sink = MemorySink::default();
625 allowed().get(&server.url, 5, &mut sink).await?;
626 assert_eq!(sink.0, b"hello");
627 }
628
629 #[xmtp_common::test(unwrap_try = true)]
630 async fn localhost_resolves_to_loopback_only() {
631 use http_body_util::BodyExt;
632
633 let server = server_async(|request| async move {
634 if request.method() == Method::PUT {
635 assert_eq!(
636 request
637 .into_body()
638 .collect()
639 .await
640 .unwrap()
641 .to_bytes()
642 .as_ref(),
643 b"body"
644 );
645 answer(StatusCode::OK, "")
646 } else {
647 answer(StatusCode::OK, "hello")
648 }
649 })
650 .await;
651 let port = Url::parse(&server.url)?.port().unwrap();
652 let transfer = Transfer::with_resolver_and_timeouts(
653 AttachmentOptions {
654 allow_private_network: true,
655 ..Default::default()
656 },
657 Arc::new(FakeResolver(vec!["192.0.2.1:0".parse()?])),
658 Duration::from_millis(500),
659 Duration::from_secs(1),
660 )?;
661 for host in ["localhost", "LOCALHOST."] {
662 let url = format!("http://{host}:{port}/object");
663 let mut sink = MemorySink::default();
664 transfer.get(&url, 5, &mut sink).await?;
665 assert_eq!(sink.0, b"hello");
666 let (_directory, body) = staged_body()?;
667 assert_eq!(transfer.put(&upload(url), body).await?, PutOutcome::Stored);
668 }
669 }
670
671 struct FakeResolver(Vec<SocketAddr>);
672
673 struct PendingResolver;
674
675 impl Resolve for PendingResolver {
676 fn resolve(&self, _name: Name) -> Resolving {
677 Box::pin(std::future::pending())
678 }
679 }
680
681 impl Resolve for FakeResolver {
682 fn resolve(&self, _name: Name) -> Resolving {
683 let addresses = self.0.clone();
684 Box::pin(async move { Ok(Box::new(addresses.into_iter()) as Addrs) })
685 }
686 }
687
688 fn staged_body() -> std::io::Result<(tempfile::TempDir, StagedFile)> {
689 let directory = tempfile::tempdir()?;
690 let path = directory.path().join("body");
691 std::fs::write(&path, b"body")?;
692 Ok((directory, StagedFile::from_path_for_test(path)))
693 }
694
695 fn upload(url: String) -> UploadRequest {
696 UploadRequest {
697 method: "PUT".into(),
698 url,
699 headers: vec![],
700 expires_in_seconds: 60,
701 }
702 }
703
704 #[xmtp_common::test(unwrap_try = true)]
706 async fn non_put_method_is_rejected_without_request() {
707 let requests = Arc::new(AtomicUsize::new(0));
708 let count = requests.clone();
709 let server = server(move |_| {
710 count.fetch_add(1, Ordering::Relaxed);
711 answer(StatusCode::OK, "")
712 })
713 .await;
714 for method in ["POST", "put"] {
715 let (_directory, body) = staged_body()?;
716 let mut request = upload(server.url.clone());
717 request.method = method.into();
718 let result = allowed().put(&request, body).await;
719 assert_eq!(requests.load(Ordering::Relaxed), 0, "{method}");
720 assert_eq!(result.unwrap_err().cause, Cause::TargetRejected, "{method}");
721 }
722 assert_eq!(requests.load(Ordering::Relaxed), 0);
723 }
724
725 struct SeenTlsPut {
726 method: Method,
727 uri: String,
728 headers: reqwest::header::HeaderMap,
729 body: Bytes,
730 alpn: Option<Vec<u8>>,
731 }
732
733 struct TlsPutServer {
734 address: SocketAddr,
735 root: rustls::pki_types::CertificateDer<'static>,
736 task: tokio::task::JoinHandle<(bool, Option<SeenTlsPut>)>,
737 }
738
739 async fn tls_put_server() -> Result<TlsPutServer, Box<dyn Error + Send + Sync>> {
740 tls_put_server_for("localhost").await
741 }
742
743 async fn tls_put_server_for(host: &str) -> Result<TlsPutServer, Box<dyn Error + Send + Sync>> {
744 xmtp_cryptography::install_crypto_provider();
745 let ca_key = rcgen::KeyPair::generate()?;
746 let mut ca_params = rcgen::CertificateParams::new(vec!["Attachment test CA".into()])?;
747 ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
748 ca_params.key_usages = vec![
749 rcgen::KeyUsagePurpose::DigitalSignature,
750 rcgen::KeyUsagePurpose::KeyCertSign,
751 ];
752 let ca = rcgen::CertifiedIssuer::self_signed(ca_params, ca_key)?;
753 let leaf_key = rcgen::KeyPair::generate()?;
754 let mut leaf_params = rcgen::CertificateParams::new(vec![host.into()])?;
755 leaf_params.is_ca = rcgen::IsCa::ExplicitNoCa;
756 leaf_params.key_usages = vec![rcgen::KeyUsagePurpose::DigitalSignature];
757 leaf_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth];
758 let leaf = leaf_params.signed_by(&leaf_key, &ca)?;
759 let mut tls = rustls::ServerConfig::builder()
760 .with_no_client_auth()
761 .with_single_cert(
762 vec![leaf.der().clone(), ca.der().clone()],
763 rustls::pki_types::PrivatePkcs8KeyDer::from(leaf_key.serialize_der()).into(),
764 )?;
765 tls.alpn_protocols = vec![b"http/1.1".to_vec()];
766 let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(tls));
767 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
768 let address = listener.local_addr()?;
769 let task = tokio::spawn(async move {
770 use http_body_util::BodyExt;
771
772 let (socket, _) = listener.accept().await.unwrap();
773 let Ok(tls) = acceptor.accept(socket).await else {
774 return (false, None);
775 };
776 let alpn = tls.get_ref().1.alpn_protocol().map(<[u8]>::to_vec);
777 let seen = Arc::new(std::sync::Mutex::new(None));
778 let captured = seen.clone();
779 let service = service_fn(move |request: Request<Incoming>| {
780 let captured = captured.clone();
781 let alpn = alpn.clone();
782 async move {
783 let (parts, body) = request.into_parts();
784 let body = body.collect().await.unwrap().to_bytes();
785 *captured.lock().unwrap() = Some(SeenTlsPut {
786 method: parts.method,
787 uri: parts.uri.to_string(),
788 headers: parts.headers,
789 body,
790 alpn,
791 });
792 Ok::<_, std::convert::Infallible>(answer(StatusCode::CREATED, ""))
793 }
794 });
795 let _ = http1::Builder::new()
796 .serve_connection(TokioIo::new(tls), service)
797 .await;
798 let observed = seen.lock().unwrap().take();
799 (true, observed)
800 });
801 Ok(TlsPutServer {
802 address,
803 root: ca.der().clone(),
804 task,
805 })
806 }
807
808 fn trusted_upload(server: &TlsPutServer) -> Result<Transfer, Box<dyn Error + Send + Sync>> {
809 let mut roots = rustls::RootCertStore::empty();
810 roots.add(server.root.clone())?;
811 let mut transfer = Transfer::with_resolver(
812 AttachmentOptions::default(),
813 Arc::new(FakeResolver(vec![server.address])),
814 )?;
815 transfer.upload_test_roots = Some(roots);
816 Ok(transfer)
817 }
818
819 async fn put_tries_next_address_after_setup_failure(
820 failure: upload::SocketSetupFailure,
821 ) -> Result<(), Box<dyn Error + Send + Sync>> {
822 let server = tls_put_server_for("upload.example").await?;
823 let mut roots = rustls::RootCertStore::empty();
824 roots.add(server.root.clone())?;
825 let mut transfer = Transfer::with_resolver_and_timeouts(
826 AttachmentOptions::default(),
827 Arc::new(FakeResolver(vec!["192.0.2.1:0".parse()?, server.address])),
828 Duration::from_secs(2),
829 Duration::from_secs(2),
830 )?;
831 transfer.upload_test_roots = Some(roots);
832 transfer.upload_socket_failure = Some(failure);
833 let (_directory, body) = staged_body()?;
834 let request = upload(format!(
835 "https://upload.example:{}/object",
836 server.address.port()
837 ));
838 assert_eq!(transfer.put(&request, body).await?, PutOutcome::Stored);
839 let (handshake, seen) = tokio::time::timeout(Duration::from_secs(2), server.task).await??;
840 assert!(handshake);
841 assert_eq!(seen.unwrap().body.as_ref(), b"body");
842 Ok(())
843 }
844
845 #[xmtp_common::test(unwrap_try = true)]
846 async fn put_tries_next_address_after_socket_creation_failure() {
847 put_tries_next_address_after_setup_failure(upload::SocketSetupFailure::CreateFirst).await?;
848 }
849
850 #[xmtp_common::test(unwrap_try = true)]
851 async fn put_tries_next_address_after_send_buffer_failure() {
852 put_tries_next_address_after_setup_failure(upload::SocketSetupFailure::SetBufferFirst)
853 .await?;
854 }
855
856 #[xmtp_common::test(unwrap_try = true)]
857 async fn put_tries_next_address_after_stalled_connect() {
858 let server = tls_put_server_for("upload.example").await?;
859 let mut roots = rustls::RootCertStore::empty();
860 roots.add(server.root.clone())?;
861 let addresses = vec!["192.0.2.1:0".parse()?, server.address];
862 let mut transfer = Transfer::with_resolver_and_timeouts(
863 AttachmentOptions::default(),
864 Arc::new(FakeResolver(addresses.clone())),
865 Duration::from_millis(1200),
866 Duration::from_secs(2),
867 )?;
868 transfer.upload_test_roots = Some(roots);
869 transfer.upload_socket_failure = Some(upload::SocketSetupFailure::ConnectFirstPending);
870 let request = upload(format!(
871 "https://upload.example:{}/object",
872 server.address.port()
873 ));
874 let (_directory, body) = staged_body()?;
875 assert_eq!(transfer.put(&request, body).await?, PutOutcome::Stored);
876 let (_, seen) = server.task.await?;
877 assert_eq!(seen.unwrap().body.as_ref(), b"body");
878
879 let mut stalled = Transfer::with_resolver_and_timeouts(
880 AttachmentOptions::default(),
881 Arc::new(FakeResolver(addresses)),
882 Duration::from_millis(300),
883 Duration::from_secs(2),
884 )?;
885 stalled.upload_socket_failure = Some(upload::SocketSetupFailure::ConnectAllPending);
886 let (_directory, body) = staged_body()?;
887 assert_eq!(
888 stalled.put(&request, body).await.unwrap_err().cause,
889 Cause::Network
890 );
891 }
892
893 #[xmtp_common::test(unwrap_try = true)]
894 async fn cloned_staged_uploads_read_independent_offsets() {
895 use http_body_util::BodyExt;
896
897 let directory = tempfile::tempdir()?;
898 let store = NativeStore::new(directory.path()).await?;
899 let payload = vec![b'x'; 4 * 1024 * 1024];
900 let mut writer = store.create_temp(".tmp/body").await?;
901 writer.write(&payload).await?;
902 store.sync(&mut writer).await?;
903 drop(writer);
904 let staged = store.open_read(".tmp/body").await?;
905 let received = Arc::new(std::sync::Mutex::new(Vec::new()));
906 let seen = received.clone();
907 let server = server_async(move |request| {
908 let seen = seen.clone();
909 async move {
910 assert_eq!(
911 request.headers()[hyper::header::CONTENT_LENGTH],
912 (4 * 1024 * 1024).to_string()
913 );
914 if let Ok(body) = request.into_body().collect().await {
915 seen.lock().unwrap().push(body.to_bytes());
916 }
917 answer(StatusCode::OK, "")
918 }
919 })
920 .await;
921 let transfer = Transfer::with_timeouts(
922 AttachmentOptions::default(),
923 Duration::from_secs(2),
924 Duration::from_secs(2),
925 )?;
926 let request = upload(format!("{}/object", server.url));
927 let (first, second) = tokio::join!(
928 transfer.put(&request, staged.clone()),
929 transfer.put(&request, staged)
930 );
931 assert_eq!(first?, PutOutcome::Stored);
932 assert_eq!(second?, PutOutcome::Stored);
933 {
934 let bodies = received.lock().unwrap();
935 assert_eq!(bodies.len(), 2);
936 assert!(bodies.iter().all(|body| body.as_ref() == payload));
937 }
938
939 let (advanced_tx, advanced_rx) = tokio::sync::oneshot::channel();
941 let signal = Arc::new(std::sync::Mutex::new(Some(advanced_tx)));
942 let received = Arc::new(std::sync::Mutex::new(Vec::new()));
943 let seen = received.clone();
944 let late_server = server_async(move |request| {
945 let signal = signal.clone();
946 let seen = seen.clone();
947 async move {
948 let first = request.uri().path() == "/first";
949 let mut body = request.into_body();
950 let mut bytes = Vec::new();
951 if first && let Some(Ok(frame)) = body.frame().await {
952 bytes.extend_from_slice(&frame.into_data().unwrap());
953 let sender = { signal.lock().unwrap().take() };
954 if let Some(sender) = sender {
955 let _ = sender.send(());
956 }
957 tokio::time::sleep(Duration::from_millis(100)).await;
958 }
959 while let Some(Ok(frame)) = body.frame().await {
960 bytes.extend_from_slice(&frame.into_data().unwrap());
961 }
962 seen.lock().unwrap().push(Bytes::from(bytes));
963 answer(StatusCode::OK, "")
964 }
965 })
966 .await;
967 let transfer = Arc::new(transfer);
968 let staged = store.open_read(".tmp/body").await?;
969 let first_request = upload(format!("{}/first", late_server.url));
970 let second_request = upload(format!("{}/second", late_server.url));
971 let first_transfer = transfer.clone();
972 let first_staged = staged.clone();
973 let first =
974 tokio::spawn(async move { first_transfer.put(&first_request, first_staged).await });
975 tokio::time::timeout(Duration::from_secs(2), advanced_rx).await??;
976 assert_eq!(
977 transfer.put(&second_request, staged).await?,
978 PutOutcome::Stored
979 );
980 assert_eq!(first.await??, PutOutcome::Stored);
981 let bodies = received.lock().unwrap();
982 assert_eq!(bodies.len(), 2);
983 assert!(bodies.iter().all(|body| body.as_ref() == payload));
984 }
985
986 #[xmtp_common::test(unwrap_try = true)]
987 async fn https_put_uses_trusted_ca_and_preserves_signed_request() {
988 let server = tls_put_server().await?;
989 let transfer = trusted_upload(&server)?;
990 let directory = tempfile::tempdir()?;
991 let path = directory.path().join("body");
992 let payload = vec![0x5a; 2 * CHUNK_SIZE];
993 std::fs::write(&path, &payload)?;
994 let mut request = upload(format!(
995 "https://localhost:{}/signed/object?signature=test",
996 server.address.port()
997 ));
998 request.headers = vec![
999 ("content-type".into(), "application/octet-stream".into()),
1000 ("content-length".into(), payload.len().to_string()),
1001 ("x-amz-meta-request".into(), "signed-value".into()),
1002 ];
1003 assert_eq!(
1004 tokio::time::timeout(
1005 Duration::from_secs(3),
1006 transfer.put(&request, StagedFile::from_path_for_test(path))
1007 )
1008 .await??,
1009 PutOutcome::Stored
1010 );
1011 let (handshake, seen) = tokio::time::timeout(Duration::from_secs(3), server.task).await??;
1012 assert!(handshake);
1013 let seen = seen.expect("server received the PUT");
1014 assert_eq!(seen.method, Method::PUT);
1015 assert_eq!(seen.uri, "/signed/object?signature=test");
1016 assert_eq!(seen.headers["content-type"], "application/octet-stream");
1017 assert_eq!(
1018 seen.headers["content-length"].to_str()?,
1019 payload.len().to_string()
1020 );
1021 assert_eq!(seen.headers["x-amz-meta-request"], "signed-value");
1022 assert_eq!(seen.body, payload);
1023 assert_eq!(seen.alpn.as_deref(), Some(&b"http/1.1"[..]));
1024 }
1025
1026 #[xmtp_common::test(unwrap_try = true)]
1027 async fn https_put_rejects_untrusted_certificate() {
1028 let server = tls_put_server().await?;
1029 let transfer = Transfer::with_resolver(
1030 AttachmentOptions::default(),
1031 Arc::new(FakeResolver(vec![server.address])),
1032 )?;
1033 let (_directory, body) = staged_body()?;
1034 let request = upload(format!(
1035 "https://localhost:{}/object",
1036 server.address.port()
1037 ));
1038 assert_eq!(
1039 tokio::time::timeout(Duration::from_secs(3), transfer.put(&request, body))
1040 .await?
1041 .unwrap_err()
1042 .cause,
1043 Cause::Network
1044 );
1045 let (handshake, seen) = tokio::time::timeout(Duration::from_secs(3), server.task).await??;
1046 assert!(!handshake);
1047 assert!(seen.is_none());
1048 }
1049
1050 #[xmtp_common::test(unwrap_try = true)]
1051 async fn https_put_rejects_wrong_hostname() {
1052 let server = tls_put_server().await?;
1053 let transfer = trusted_upload(&server)?;
1054 let (_directory, body) = staged_body()?;
1055 let request = upload(format!(
1056 "https://wrong.example:{}/object",
1057 server.address.port()
1058 ));
1059 assert_eq!(
1060 tokio::time::timeout(Duration::from_secs(3), transfer.put(&request, body))
1061 .await?
1062 .unwrap_err()
1063 .cause,
1064 Cause::Network
1065 );
1066 let (handshake, seen) = tokio::time::timeout(Duration::from_secs(3), server.task).await??;
1067 assert!(!handshake);
1068 assert!(seen.is_none());
1069 }
1070
1071 #[xmtp_common::test(unwrap_try = true)]
1073 async fn public_http_put_is_rejected_before_request() {
1074 let seen = Arc::new(AtomicBool::new(false));
1075 let flag = seen.clone();
1076 let server = server(move |_| {
1077 flag.store(true, Ordering::Relaxed);
1078 answer(StatusCode::OK, "")
1079 })
1080 .await;
1081 let address: SocketAddr = server.url.trim_start_matches("http://").parse()?;
1082 let transfer = Transfer::with_resolver(
1083 AttachmentOptions::default(),
1084 Arc::new(FakeResolver(vec![address])),
1085 )?;
1086 let (_directory, body) = staged_body()?;
1087 let request = upload(format!("http://example.com:{}/object", address.port()));
1088 assert_eq!(
1089 transfer.put(&request, body).await.unwrap_err().cause,
1090 Cause::InsecureUrl
1091 );
1092 assert!(!seen.load(Ordering::Relaxed));
1093 }
1094
1095 #[xmtp_common::test(unwrap_try = true)]
1097 async fn loopback_http_put_needs_no_private_network_flag() {
1098 use http_body_util::BodyExt;
1099
1100 let server = server_async(|request| async move {
1101 let body = request.into_body().collect().await.unwrap().to_bytes();
1102 assert_eq!(body.as_ref(), b"body");
1103 answer(StatusCode::ACCEPTED, "")
1104 })
1105 .await;
1106 let (_directory, body) = staged_body()?;
1107 let transfer = Transfer::new(AttachmentOptions::default())?;
1108 assert_eq!(
1109 transfer.put(&upload(server.url.clone()), body).await?,
1110 PutOutcome::Stored
1111 );
1112 }
1113
1114 #[xmtp_common::test(unwrap_try = true)]
1116 async fn private_https_put_is_not_dns_blocked() {
1117 crate::http::secure_upload_url(&Url::parse("https://10.1.2.3/object")?)?;
1118 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
1119 let address = listener.local_addr()?;
1120 let transfer = Transfer::with_resolver_and_timeouts(
1121 AttachmentOptions::default(),
1122 Arc::new(FakeResolver(vec![address])),
1123 Duration::from_secs(1),
1124 Duration::from_secs(1),
1125 )?;
1126 let (_directory, body) = staged_body()?;
1127 let accepted = tokio::spawn(async move {
1128 let (socket, _) = listener.accept().await.unwrap();
1129 drop(socket);
1130 });
1131 let request = upload(format!("https://private.example:{}/object", address.port()));
1132 assert_eq!(
1133 transfer.put(&request, body).await.unwrap_err().cause,
1134 Cause::Network
1135 );
1136 tokio::time::timeout(Duration::from_secs(1), accepted).await??;
1137 }
1138
1139 #[xmtp_common::test(unwrap_try = true)]
1141 async fn put_redirect_is_rejected_without_following() {
1142 let followed = Arc::new(AtomicBool::new(false));
1143 let flag = followed.clone();
1144 let target = server(move |_| {
1145 flag.store(true, Ordering::Relaxed);
1146 answer(StatusCode::OK, "")
1147 })
1148 .await;
1149 let location = target.url.clone();
1150 let origin = server(move |_| {
1151 Response::builder()
1152 .status(StatusCode::TEMPORARY_REDIRECT)
1153 .header(LOCATION, &location)
1154 .body(Full::new(Bytes::new()))
1155 .unwrap()
1156 })
1157 .await;
1158 let (_directory, body) = staged_body()?;
1159 let transfer = Transfer::new(AttachmentOptions::default())?;
1160 assert_eq!(
1161 transfer
1162 .put(&upload(origin.url.clone()), body)
1163 .await
1164 .unwrap_err()
1165 .cause,
1166 Cause::TargetRejected
1167 );
1168 assert!(!followed.load(Ordering::Relaxed));
1169 }
1170
1171 async fn early_put_status(
1172 status: StatusCode,
1173 ) -> Result<Result<PutOutcome, AttachmentError>, Box<dyn Error + Send + Sync>> {
1174 use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
1175
1176 const BODY_SIZE: usize = 8 * 1024 * 1024;
1177 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
1178 let url = format!("http://{}", listener.local_addr()?);
1179 let server = tokio::spawn(async move {
1180 let (socket, _) = listener.accept().await.unwrap();
1181 let mut reader = BufReader::new(socket);
1182 let mut line = String::new();
1183 let mut content_length = None;
1184 loop {
1185 line.clear();
1186 assert!(reader.read_line(&mut line).await.unwrap() > 0);
1187 if line == "\r\n" {
1188 break;
1189 }
1190 if let Some(length) = line.to_ascii_lowercase().strip_prefix("content-length: ") {
1191 content_length = Some(length.trim().parse::<usize>().unwrap());
1192 }
1193 }
1194 assert_eq!(content_length, Some(BODY_SIZE));
1195 reader
1196 .get_mut()
1197 .write_all(
1198 format!(
1199 "HTTP/1.1 {} {}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n",
1200 status.as_u16(),
1201 status.canonical_reason().unwrap()
1202 )
1203 .as_bytes(),
1204 )
1205 .await
1206 .unwrap();
1207 let mut discarded = 0;
1208 let mut buffer = [0_u8; 8192];
1209 let _ = tokio::time::timeout(Duration::from_millis(200), async {
1210 while discarded < 256 * 1024 {
1211 let limit = (256 * 1024 - discarded).min(buffer.len());
1212 let read = reader.read(&mut buffer[..limit]).await.unwrap();
1213 if read == 0 {
1214 break;
1215 }
1216 discarded += read;
1217 }
1218 })
1219 .await;
1220 assert!(discarded <= 256 * 1024);
1221 });
1222 let directory = tempfile::tempdir()?;
1223 let path = directory.path().join("body");
1224 std::fs::write(&path, vec![0x5a; BODY_SIZE])?;
1225 let result = tokio::time::timeout(
1226 Duration::from_secs(3),
1227 allowed().put(&upload(url), StagedFile::from_path_for_test(path)),
1228 )
1229 .await?;
1230 tokio::time::timeout(Duration::from_secs(1), server).await??;
1231 Ok(result)
1232 }
1233
1234 #[xmtp_common::test(unwrap_try = true)]
1236 async fn put_early_412_with_large_body() {
1237 assert_eq!(
1238 early_put_status(StatusCode::PRECONDITION_FAILED).await??,
1239 PutOutcome::AlreadyStored
1240 );
1241 }
1242
1243 #[xmtp_common::test(unwrap_try = true)]
1245 async fn put_early_403_with_large_body() {
1246 assert_eq!(
1247 early_put_status(StatusCode::FORBIDDEN)
1248 .await?
1249 .unwrap_err()
1250 .cause,
1251 Cause::TargetRejected
1252 );
1253 }
1254
1255 #[xmtp_common::test(unwrap_try = true)]
1257 async fn put_rejection_keeps_http_status() {
1258 let error = early_put_status(StatusCode::FORBIDDEN).await?.unwrap_err();
1259 assert_eq!(error.cause, Cause::TargetRejected);
1260 assert_eq!(error.http_status, Some(403));
1261 }
1262
1263 #[xmtp_common::test(unwrap_try = true)]
1265 async fn put_early_2xx_waits_for_body() {
1266 use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
1267
1268 const BODY_SIZE: usize = 4 * 1024 * 1024;
1269 let socket = tokio::net::TcpSocket::new_v4()?;
1270 socket.set_recv_buffer_size(8192)?;
1271 socket.bind("127.0.0.1:0".parse()?)?;
1272 let listener = socket.listen(1)?;
1273 let url = format!("http://{}", listener.local_addr()?);
1274 let received = Arc::new(AtomicUsize::new(0));
1275 let observed = received.clone();
1276 let (paused_tx, paused_rx) = tokio::sync::oneshot::channel();
1277 let (resume_tx, resume_rx) = tokio::sync::oneshot::channel();
1278 let server = tokio::spawn(async move {
1279 let (socket, _) = listener.accept().await.unwrap();
1280 let mut reader = BufReader::new(socket);
1281 let mut line = String::new();
1282 let mut content_length = None;
1283 loop {
1284 line.clear();
1285 assert!(reader.read_line(&mut line).await.unwrap() > 0);
1286 if line == "\r\n" {
1287 break;
1288 }
1289 if let Some(length) = line.to_ascii_lowercase().strip_prefix("content-length: ") {
1290 content_length = Some(length.trim().parse::<usize>().unwrap());
1291 }
1292 }
1293 assert_eq!(content_length, Some(BODY_SIZE));
1294 reader
1295 .get_mut()
1296 .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\n")
1297 .await
1298 .unwrap();
1299 let mut buffer = [0_u8; 8192];
1300 let mut count = 0;
1301 let mut paused_tx = Some(paused_tx);
1302 let mut resume_rx = Some(resume_rx);
1303 while count < BODY_SIZE {
1304 let limit = (BODY_SIZE - count).min(buffer.len());
1305 let size = reader.read(&mut buffer[..limit]).await.unwrap();
1306 assert!(size > 0);
1307 assert!(buffer[..size].iter().all(|byte| *byte == 0x5a));
1308 count += size;
1309 observed.store(count, Ordering::Release);
1310 if count >= BODY_SIZE / 2 && paused_tx.is_some() {
1311 paused_tx.take().unwrap().send(()).unwrap();
1312 resume_rx.take().unwrap().await.unwrap();
1313 }
1314 }
1315 reader.get_mut().write_all(b"x").await.unwrap();
1316 });
1317 let directory = tempfile::tempdir()?;
1318 let path = directory.path().join("body");
1319 std::fs::write(&path, vec![0x5a; BODY_SIZE])?;
1320 let mut upload = tokio::spawn(async move {
1321 allowed()
1322 .put(&upload(url), StagedFile::from_path_for_test(path))
1323 .await
1324 });
1325 tokio::time::timeout(Duration::from_secs(5), paused_rx).await??;
1326 assert!(received.load(Ordering::Acquire) < BODY_SIZE);
1327 assert!(
1328 tokio::time::timeout(Duration::from_millis(150), &mut upload)
1329 .await
1330 .is_err()
1331 );
1332 resume_tx.send(()).unwrap();
1333 assert_eq!(
1334 tokio::time::timeout(Duration::from_secs(5), &mut upload).await???,
1335 PutOutcome::Stored
1336 );
1337 tokio::time::timeout(Duration::from_secs(5), server).await??;
1338 assert_eq!(received.load(Ordering::Acquire), BODY_SIZE);
1339 }
1340
1341 #[xmtp_common::test(unwrap_try = true)]
1343 async fn put_early_2xx_then_close_is_network() {
1344 assert_eq!(
1345 early_put_status(StatusCode::OK).await?.unwrap_err().cause,
1346 Cause::Network
1347 );
1348 }
1349
1350 async fn changed_file_during_put(
1351 truncate: bool,
1352 ) -> Result<(Result<PutOutcome, AttachmentError>, usize), Box<dyn Error + Send + Sync>> {
1353 use std::io::Write;
1354 use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
1355
1356 const BODY_SIZE: usize = 8 * 1024 * 1024;
1357 let socket = tokio::net::TcpSocket::new_v4()?;
1358 socket.set_recv_buffer_size(8192)?;
1359 socket.bind("127.0.0.1:0".parse()?)?;
1360 let listener = socket.listen(1)?;
1361 let url = format!("http://{}", listener.local_addr()?);
1362 let (headers_tx, headers_rx) = tokio::sync::oneshot::channel();
1363 let (resume_tx, resume_rx) = tokio::sync::oneshot::channel();
1364 let server = tokio::spawn(async move {
1365 let (socket, _) = listener.accept().await.unwrap();
1366 let mut reader = BufReader::new(socket);
1367 let mut line = String::new();
1368 let mut content_length = None;
1369 loop {
1370 line.clear();
1371 assert!(reader.read_line(&mut line).await.unwrap() > 0);
1372 if line == "\r\n" {
1373 break;
1374 }
1375 if let Some(length) = line.to_ascii_lowercase().strip_prefix("content-length: ") {
1376 content_length = Some(length.trim().parse::<usize>().unwrap());
1377 }
1378 }
1379 assert_eq!(content_length, Some(BODY_SIZE));
1380 reader
1381 .get_mut()
1382 .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
1383 .await
1384 .unwrap();
1385 headers_tx.send(()).unwrap();
1386 resume_rx.await.unwrap();
1387 let mut buffer = [0_u8; 8192];
1388 let mut received = 0;
1389 loop {
1390 let size = reader.read(&mut buffer).await.unwrap();
1391 if size == 0 {
1392 break;
1393 }
1394 assert!(buffer[..size].iter().all(|byte| *byte == 0x5a));
1395 received += size;
1396 }
1397 received
1398 });
1399 let directory = tempfile::tempdir()?;
1400 let path = directory.path().join("body");
1401 std::fs::write(&path, vec![0x5a; BODY_SIZE])?;
1402 let upload_task = tokio::spawn({
1403 let path = path.clone();
1404 async move {
1405 allowed()
1406 .put(&upload(url), StagedFile::from_path_for_test(path))
1407 .await
1408 }
1409 });
1410 tokio::time::timeout(Duration::from_secs(5), headers_rx).await??;
1411 if truncate {
1412 std::fs::OpenOptions::new()
1413 .write(true)
1414 .open(&path)?
1415 .set_len(512 * 1024)?;
1416 } else {
1417 std::fs::OpenOptions::new()
1418 .append(true)
1419 .open(&path)?
1420 .write_all(b"extra")?;
1421 }
1422 resume_tx.send(()).unwrap();
1423 let result = tokio::time::timeout(Duration::from_secs(5), upload_task).await??;
1424 let received = tokio::time::timeout(Duration::from_secs(5), server).await??;
1425 Ok((result, received))
1426 }
1427
1428 #[xmtp_common::test(unwrap_try = true)]
1429 async fn staged_file_growth_during_put_sends_declared_length() {
1430 let (result, received) = changed_file_during_put(false).await?;
1431 assert_eq!(received, 8 * 1024 * 1024);
1432 assert_eq!(result?, PutOutcome::Stored);
1433 }
1434
1435 #[xmtp_common::test(unwrap_try = true)]
1436 async fn staged_file_truncation_during_put_is_local_storage() {
1437 let (result, _) = changed_file_during_put(true).await?;
1438 assert_eq!(result.unwrap_err().cause, Cause::LocalStorage);
1439 }
1440
1441 #[xmtp_common::test(unwrap_try = true)]
1443 async fn downloads_ignore_proxy_environment() {
1444 let proxy = std::net::TcpListener::bind("127.0.0.1:0")?;
1445 let proxy_url = format!("http://{}", proxy.local_addr()?);
1446 let output = std::process::Command::new(std::env::current_exe()?)
1447 .args(["--exact", "http::native::tests::proxy_environment_child"])
1448 .env("XMTP_ATTACHMENTS_PROXY_CHILD", "1")
1449 .env("HTTP_PROXY", &proxy_url)
1450 .env("HTTPS_PROXY", &proxy_url)
1451 .env("ALL_PROXY", &proxy_url)
1452 .env("NO_PROXY", "")
1453 .output()?;
1454 assert!(
1455 output.status.success(),
1456 "child stdout: {}\nchild stderr: {}",
1457 String::from_utf8_lossy(&output.stdout),
1458 String::from_utf8_lossy(&output.stderr)
1459 );
1460 }
1461
1462 #[xmtp_common::test(unwrap_try = true)]
1463 async fn proxy_environment_child() {
1464 if std::env::var_os("XMTP_ATTACHMENTS_PROXY_CHILD").is_none() {
1465 return;
1466 }
1467 let target = server(|_| answer(StatusCode::OK, "direct")).await;
1468 let mut sink = MemorySink::default();
1469 allowed().get(&target.url, 6, &mut sink).await?;
1470 assert_eq!(sink.0, b"direct");
1471
1472 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
1473 let address = listener.local_addr()?;
1474 let transfer = Transfer::with_resolver_and_timeouts(
1475 AttachmentOptions {
1476 allow_private_network: true,
1477 ..Default::default()
1478 },
1479 Arc::new(FakeResolver(vec![address])),
1480 Duration::from_secs(1),
1481 Duration::from_secs(1),
1482 )?;
1483 let task = tokio::spawn(async move {
1484 transfer
1485 .get(
1486 &format!("https://example.test:{}/object", address.port()),
1487 1,
1488 &mut MemorySink::default(),
1489 )
1490 .await
1491 });
1492 let (socket, _) = tokio::time::timeout(Duration::from_secs(1), listener.accept()).await??;
1493 drop(socket);
1494 assert_eq!(
1495 tokio::time::timeout(Duration::from_secs(2), task)
1496 .await??
1497 .unwrap_err()
1498 .cause,
1499 Cause::Network
1500 );
1501 }
1502
1503 #[xmtp_common::test(unwrap_try = true)]
1505 async fn blocks_private_after_dns() {
1506 for address in ["10.0.0.1", "::ffff:10.0.0.1", "64:ff9b::a00:1"] {
1507 let address: IpAddr = address.parse()?;
1508 let resolver = Arc::new(FakeResolver(vec![SocketAddr::new(address, 443)]));
1509 let guarded = GuardedResolver {
1510 upstream: resolver.clone(),
1511 allow_private: false,
1512 };
1513 let failure = guarded.resolve("example.test".parse()?).await;
1514 assert!(failure.err().unwrap().is::<BlockedDns>());
1515 let transfer = Transfer::with_resolver(AttachmentOptions::default(), resolver)?;
1516 let error = transfer
1517 .get(
1518 "https://example.test/object",
1519 10,
1520 &mut MemorySink::default(),
1521 )
1522 .await
1523 .unwrap_err();
1524 assert_eq!(error.cause, Cause::BlockedAddress);
1525 }
1526 }
1527
1528 #[xmtp_common::test(unwrap_try = true)]
1530 async fn blocks_ip_literal() {
1531 for url in [
1532 "https://10.0.0.1/object",
1533 "https://[::ffff:10.0.0.1]/object",
1534 ] {
1535 let error = Transfer::new(AttachmentOptions::default())?
1536 .get(url, 10, &mut MemorySink::default())
1537 .await
1538 .unwrap_err();
1539 assert_eq!(error.cause, Cause::BlockedAddress);
1540 }
1541 }
1542
1543 #[xmtp_common::test(unwrap_try = true)]
1545 async fn redirect_rechecked() {
1546 let server = server(|_| {
1547 Response::builder()
1548 .status(StatusCode::FOUND)
1549 .header(LOCATION, "http://10.0.0.1/private")
1550 .body(Full::new(Bytes::new()))
1551 .unwrap()
1552 })
1553 .await;
1554 let error = allowed()
1555 .get(&server.url, 10, &mut MemorySink::default())
1556 .await
1557 .unwrap_err();
1558 assert_eq!(error.cause, Cause::InsecureUrl);
1560 let public = Url::parse("https://public.example/object")?;
1561 assert_eq!(
1562 redirect_target(
1563 &public,
1564 "https://10.0.0.1/private",
1565 0,
1566 &AttachmentOptions::default(),
1567 )
1568 .unwrap_err()
1569 .cause,
1570 Cause::BlockedAddress
1571 );
1572 }
1573
1574 #[xmtp_common::test(unwrap_try = true)]
1575 async fn not_modified_with_location_is_http_status() {
1576 let server = server(|_| {
1577 Response::builder()
1578 .status(StatusCode::NOT_MODIFIED)
1579 .header(LOCATION, "http://example.com/other")
1580 .body(Full::new(Bytes::new()))
1581 .unwrap()
1582 })
1583 .await;
1584 let error = allowed()
1585 .get(&server.url, 1, &mut MemorySink::default())
1586 .await
1587 .unwrap_err();
1588 assert_eq!(error.cause, Cause::HttpStatus);
1589 assert_eq!(error.http_status, Some(304));
1590 }
1591
1592 #[xmtp_common::test(unwrap_try = true)]
1594 async fn ten_redirects_succeed() {
1595 let server = server(|request| {
1596 let n: u8 = request
1597 .uri()
1598 .path()
1599 .trim_start_matches('/')
1600 .parse()
1601 .unwrap();
1602 if n < 10 {
1603 Response::builder()
1604 .status(StatusCode::FOUND)
1605 .header(LOCATION, format!("/{}", n + 1))
1606 .body(Full::new(Bytes::new()))
1607 .unwrap()
1608 } else {
1609 answer(StatusCode::OK, "done")
1610 }
1611 })
1612 .await;
1613 let mut sink = MemorySink::default();
1614 allowed()
1615 .get(&format!("{}/0", server.url), 10, &mut sink)
1616 .await?;
1617 assert_eq!(sink.0, b"done");
1618 }
1619
1620 #[xmtp_common::test(unwrap_try = true)]
1622 async fn eleven_redirects_fail() {
1623 let server = server(|request| {
1624 let n: u8 = request
1625 .uri()
1626 .path()
1627 .trim_start_matches('/')
1628 .parse()
1629 .unwrap_or(0);
1630 if n < 11 {
1631 Response::builder()
1632 .status(StatusCode::FOUND)
1633 .header(LOCATION, format!("/{}", n + 1))
1634 .body(Full::new(Bytes::new()))
1635 .unwrap()
1636 } else {
1637 answer(StatusCode::OK, "done")
1638 }
1639 })
1640 .await;
1641 let error = allowed()
1642 .get(&format!("{}/0", server.url), 10, &mut MemorySink::default())
1643 .await
1644 .unwrap_err();
1645 assert_eq!(error.cause, Cause::TooManyRedirects);
1646 }
1647
1648 #[xmtp_common::test(unwrap_try = true)]
1650 async fn gzip_body_capped_after_decoding() {
1651 use std::io::Write;
1652 let mut gzip = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
1653 gzip.write_all(&vec![b'x'; 128 * 1024])?;
1654 let compressed = Bytes::from(gzip.finish()?);
1655 let server = server(move |_| {
1656 Response::builder()
1657 .status(StatusCode::OK)
1658 .header(CONTENT_ENCODING, "gzip")
1659 .body(Full::new(compressed.clone()))
1660 .unwrap()
1661 })
1662 .await;
1663 let error = allowed()
1664 .get(&server.url, 1024, &mut MemorySink::default())
1665 .await
1666 .unwrap_err();
1667 assert_eq!(error.cause, Cause::TooLarge);
1668 }
1669
1670 #[xmtp_common::test(unwrap_try = true)]
1672 async fn endless_empty_gzip_blocks_hit_input_cap() {
1673 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1674
1675 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
1676 let url = format!("http://{}", listener.local_addr()?);
1677 let server = tokio::spawn(async move {
1678 let (mut socket, _) = listener.accept().await.unwrap();
1679 let mut request = [0_u8; 1024];
1680 let _ = socket.read(&mut request).await;
1681 socket
1682 .write_all(
1683 b"HTTP/1.1 200 OK\r\nContent-Encoding: gzip\r\nConnection: close\r\n\r\n",
1684 )
1685 .await
1686 .unwrap();
1687 socket
1688 .write_all(&[0x1f, 0x8b, 8, 0, 0, 0, 0, 0, 0, 0xff])
1689 .await
1690 .unwrap();
1691 let empty_blocks = [0, 0, 0, 0xff, 0xff].repeat(1024);
1693 loop {
1694 if socket.write_all(&empty_blocks).await.is_err() {
1695 break;
1696 }
1697 tokio::time::sleep(Duration::from_millis(1)).await;
1698 }
1699 });
1700
1701 let mut sink = MemorySink::default();
1702 let result = timeout(Duration::from_secs(3), allowed().get(&url, 1024, &mut sink)).await;
1703 server.abort();
1704 let _ = server.await;
1705 let error = result
1706 .expect("encoded input limit ends the stream")
1707 .unwrap_err();
1708 assert_eq!(error.cause, Cause::TooLarge);
1709 assert!(sink.0.is_empty());
1710 }
1711
1712 #[xmtp_common::test(unwrap_try = true)]
1714 async fn identity_body_capped() {
1715 let server = server(|_| answer(StatusCode::OK, "five!")).await;
1716 let error = allowed()
1717 .get(&server.url, 4, &mut MemorySink::default())
1718 .await
1719 .unwrap_err();
1720 assert_eq!(error.cause, Cause::TooLarge);
1721 }
1722
1723 #[xmtp_common::test(unwrap_try = true)]
1724 async fn gzip_network_error_is_network() {
1725 use std::io::Write;
1726 let mut gzip = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
1727 gzip.write_all(&vec![b'x'; 128 * 1024])?;
1728 let compressed = gzip.finish()?;
1729 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
1730 let url = format!("http://{}", listener.local_addr()?);
1731 let task = tokio::spawn(async move {
1732 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1733 let (mut socket, _) = listener.accept().await.unwrap();
1734 let mut request = [0_u8; 1024];
1735 let _ = socket.read(&mut request).await;
1736 socket
1737 .write_all(
1738 b"HTTP/1.1 200 OK\r\nContent-Encoding: gzip\r\nContent-Length: 1000\r\n\r\n",
1739 )
1740 .await
1741 .unwrap();
1742 socket
1743 .write_all(&compressed[..compressed.len() / 2])
1744 .await
1745 .unwrap();
1746 });
1747 let error = allowed()
1748 .get(&url, 1024 * 1024, &mut MemorySink::default())
1749 .await
1750 .unwrap_err();
1751 assert_eq!(error.cause, Cause::Network);
1752 task.await?;
1753 }
1754
1755 #[xmtp_common::test(unwrap_try = true)]
1756 async fn gzip_truncated_after_member_is_network() {
1757 use std::io::Write;
1758 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1759
1760 let mut gzip = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
1761 gzip.write_all(b"complete member")?;
1762 let compressed = gzip.finish()?;
1763 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
1764 let url = format!("http://{}", listener.local_addr()?);
1765 let server = tokio::spawn(async move {
1766 let (mut socket, _) = listener.accept().await.unwrap();
1767 let mut request = [0_u8; 1024];
1768 assert!(socket.read(&mut request).await.unwrap() > 0);
1769 let head = format!(
1770 "HTTP/1.1 200 OK\r\nContent-Encoding: gzip\r\nContent-Length: {}\r\n\r\n",
1771 compressed.len() + 100
1772 );
1773 socket.write_all(head.as_bytes()).await.unwrap();
1774 socket.write_all(&compressed).await.unwrap();
1775 });
1776 let error = allowed()
1777 .get(&url, 100, &mut MemorySink::default())
1778 .await
1779 .unwrap_err();
1780 assert_eq!(error.cause, Cause::Network);
1781 server.await?;
1782 }
1783
1784 #[xmtp_common::test(unwrap_try = true)]
1785 async fn deflate_trailing_bytes_are_rejected() {
1786 use std::io::Write;
1787 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1788
1789 let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::fast());
1790 encoder.write_all(b"complete member")?;
1791 let compressed = encoder.finish()?;
1792 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
1793 let url = format!("http://{}", listener.local_addr()?);
1794 let server = tokio::spawn(async move {
1795 let (mut socket, _) = listener.accept().await.unwrap();
1796 let mut request = [0_u8; 1024];
1797 assert!(socket.read(&mut request).await.unwrap() > 0);
1798 let head = format!(
1799 "HTTP/1.1 200 OK\r\nContent-Encoding: deflate\r\nContent-Length: {}\r\n\r\n",
1800 compressed.len() + 1024
1801 );
1802 socket.write_all(head.as_bytes()).await.unwrap();
1803 socket.write_all(&compressed).await.unwrap();
1804 socket.write_all(&vec![b'x'; 1024]).await.unwrap();
1805 });
1806 let error = allowed()
1807 .get(&url, 100, &mut MemorySink::default())
1808 .await
1809 .expect_err("trailing encoded bytes must fail");
1810 assert_eq!(error.cause, Cause::HttpStatus);
1811 server.await?;
1812 }
1813
1814 #[xmtp_common::test(unwrap_try = true)]
1815 async fn deflate_short_body_after_complete_member_is_network() {
1816 use std::io::Write;
1817 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1818
1819 let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::fast());
1820 encoder.write_all(b"complete member")?;
1821 let compressed = encoder.finish()?;
1822 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
1823 let url = format!("http://{}", listener.local_addr()?);
1824 let server = tokio::spawn(async move {
1825 let (mut socket, _) = listener.accept().await.unwrap();
1826 let mut request = [0_u8; 1024];
1827 assert!(socket.read(&mut request).await.unwrap() > 0);
1828 let head = format!(
1829 "HTTP/1.1 200 OK\r\nContent-Encoding: deflate\r\nContent-Length: {}\r\n\r\n",
1830 compressed.len() + 100
1831 );
1832 socket.write_all(head.as_bytes()).await.unwrap();
1833 socket.write_all(&compressed).await.unwrap();
1834 tokio::time::sleep(Duration::from_millis(100)).await;
1835 });
1836 let error = allowed()
1837 .get(&url, 100, &mut MemorySink::default())
1838 .await
1839 .expect_err("short HTTP body must fail");
1840 assert_eq!(error.cause, Cause::Network);
1841 server.await?;
1842 }
1843
1844 #[xmtp_common::test(unwrap_try = true)]
1845 async fn gzip_and_deflate_decode_in_stream() {
1846 use std::io::Write;
1847 let content = b"decoded ciphertext";
1848 let mut gzip = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
1849 gzip.write_all(content)?;
1850 let mut deflate = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::fast());
1851 deflate.write_all(content)?;
1852 for (encoding, compressed) in [("gzip", gzip.finish()?), ("deflate", deflate.finish()?)] {
1853 let compressed = Bytes::from(compressed);
1854 let server = server(move |_| {
1855 Response::builder()
1856 .status(StatusCode::OK)
1857 .header(CONTENT_ENCODING, encoding)
1858 .body(Full::new(compressed.clone()))
1859 .unwrap()
1860 })
1861 .await;
1862 let mut sink = MemorySink::default();
1863 allowed()
1864 .get(&server.url, content.len() as u64, &mut sink)
1865 .await?;
1866 assert_eq!(sink.0, content);
1867 }
1868 }
1869
1870 #[xmtp_common::test(unwrap_try = true)]
1871 async fn gzip_multi_member_body_decodes() {
1872 use std::io::Write;
1873 let mut compressed = Vec::new();
1874 for part in [b"first".as_slice(), b"second".as_slice()] {
1875 let mut gzip = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
1876 gzip.write_all(part)?;
1877 compressed.extend(gzip.finish()?);
1878 }
1879 let compressed = Bytes::from(compressed);
1880 let server = server(move |_| {
1881 Response::builder()
1882 .header(CONTENT_ENCODING, "gzip")
1883 .body(Full::new(compressed.clone()))
1884 .unwrap()
1885 })
1886 .await;
1887 let mut sink = MemorySink::default();
1888 allowed().get(&server.url, 11, &mut sink).await?;
1889 assert_eq!(sink.0, b"firstsecond");
1890 }
1891
1892 #[xmtp_common::test(unwrap_try = true)]
1893 async fn dropped_gzip_download_closes_connection() {
1894 use std::io::Write;
1895 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1896
1897 struct SignalSink(Option<tokio::sync::oneshot::Sender<()>>);
1898 #[async_trait::async_trait]
1899 impl DownloadSink for SignalSink {
1900 async fn write(&mut self, _bytes: &[u8]) -> Result<(), AttachmentError> {
1901 if let Some(signal) = self.0.take() {
1902 let _ = signal.send(());
1903 }
1904 Ok(())
1905 }
1906 }
1907
1908 let mut gzip = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
1909 gzip.write_all(b"decoded")?;
1910 gzip.flush()?;
1911 let incomplete = gzip.get_ref().clone();
1912 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
1913 let url = format!("http://{}", listener.local_addr()?);
1914 let server = tokio::spawn(async move {
1915 let (mut socket, _) = listener.accept().await.unwrap();
1916 let mut request = [0_u8; 1024];
1917 assert!(socket.read(&mut request).await.unwrap() > 0);
1918 socket
1919 .write_all(b"HTTP/1.1 200 OK\r\nContent-Encoding: gzip\r\n\r\n")
1920 .await
1921 .unwrap();
1922 socket.write_all(&incomplete).await.unwrap();
1923 let mut discarded = Vec::new();
1924 socket.read_to_end(&mut discarded).await.unwrap();
1925 });
1926 let (written_tx, written_rx) = tokio::sync::oneshot::channel();
1927 let task = tokio::spawn(async move {
1928 allowed()
1929 .get(&url, 100, &mut SignalSink(Some(written_tx)))
1930 .await
1931 });
1932 tokio::time::timeout(Duration::from_secs(3), written_rx).await??;
1933 task.abort();
1934 let _ = task.await;
1935 tokio::time::timeout(Duration::from_secs(2), server).await??;
1936 }
1937
1938 #[xmtp_common::test(unwrap_try = true)]
1939 async fn cancelled_put_after_early_2xx_closes_connection() {
1940 use tokio::io::{AsyncReadExt, AsyncWriteExt};
1941
1942 const BODY_SIZE: usize = 8 * 1024 * 1024;
1943 let socket = tokio::net::TcpSocket::new_v4()?;
1944 socket.set_recv_buffer_size(8192)?;
1945 socket.bind("127.0.0.1:0".parse()?)?;
1946 let listener = socket.listen(1)?;
1947 let url = format!("http://{}", listener.local_addr()?);
1948 let (answered_tx, answered_rx) = tokio::sync::oneshot::channel();
1949 let (read_tx, read_rx) = tokio::sync::oneshot::channel();
1950 let server = tokio::spawn(async move {
1951 let (mut socket, _) = listener.accept().await.unwrap();
1952 let mut head = Vec::new();
1953 let mut buf = [0_u8; 1024];
1954 while !head.windows(4).any(|bytes| bytes == b"\r\n\r\n") {
1955 let count = socket.read(&mut buf).await.unwrap();
1956 assert!(count > 0);
1957 head.extend_from_slice(&buf[..count]);
1958 }
1959 socket
1960 .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
1961 .await
1962 .unwrap();
1963 answered_tx.send(()).unwrap();
1964 read_rx.await.unwrap();
1965 let mut discarded = Vec::new();
1966 socket.read_to_end(&mut discarded).await.unwrap();
1967 assert!(head.len() + discarded.len() < BODY_SIZE);
1968 });
1969 let directory = tempfile::tempdir()?;
1970 let path = directory.path().join("body");
1971 std::fs::write(&path, vec![0x5a; BODY_SIZE])?;
1972 let task = tokio::spawn(async move {
1973 allowed()
1974 .put(&upload(url), StagedFile::from_path_for_test(path))
1975 .await
1976 });
1977 tokio::time::timeout(Duration::from_secs(3), answered_rx).await??;
1978 tokio::time::sleep(Duration::from_millis(50)).await;
1979 task.abort();
1980 let _ = task.await;
1981 read_tx.send(()).unwrap();
1982 tokio::time::timeout(Duration::from_secs(2), server).await??;
1983 }
1984
1985 #[xmtp_common::test(unwrap_try = true)]
1986 async fn content_encoding_field_lines_are_combined() {
1987 use std::io::Write;
1988 let mut gzip = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
1989 gzip.write_all(b"decoded")?;
1990 let compressed = Bytes::from(gzip.finish()?);
1991 let first = compressed.clone();
1992 let first_server = server(move |_| {
1993 Response::builder()
1994 .header(CONTENT_ENCODING, "identity")
1995 .header(CONTENT_ENCODING, "gzip")
1996 .body(Full::new(first.clone()))
1997 .unwrap()
1998 })
1999 .await;
2000 let mut sink = MemorySink::default();
2001 allowed().get(&first_server.url, 7, &mut sink).await?;
2002 assert_eq!(sink.0, b"decoded");
2003
2004 let second_server = server(move |_| {
2005 Response::builder()
2006 .header(CONTENT_ENCODING, "identity, gzip")
2007 .body(Full::new(compressed.clone()))
2008 .unwrap()
2009 })
2010 .await;
2011 let mut sink = MemorySink::default();
2012 allowed().get(&second_server.url, 7, &mut sink).await?;
2013 assert_eq!(sink.0, b"decoded");
2014
2015 let invalid_server = server(|_| {
2016 Response::builder()
2017 .header(CONTENT_ENCODING, "gzip, deflate")
2018 .body(Full::new(Bytes::new()))
2019 .unwrap()
2020 })
2021 .await;
2022 assert_eq!(
2023 allowed()
2024 .get(&invalid_server.url, 7, &mut MemorySink::default())
2025 .await
2026 .unwrap_err()
2027 .cause,
2028 Cause::HttpStatus
2029 );
2030 }
2031
2032 #[xmtp_common::test(unwrap_try = true)]
2033 async fn x_gzip_is_gzip() {
2034 use std::io::Write;
2035 let mut gzip = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
2036 gzip.write_all(b"decoded")?;
2037 let compressed = Bytes::from(gzip.finish()?);
2038 let server = server(move |_| {
2039 Response::builder()
2040 .header(CONTENT_ENCODING, "X-GZip")
2041 .body(Full::new(compressed.clone()))
2042 .unwrap()
2043 })
2044 .await;
2045 let mut sink = MemorySink::default();
2046 allowed().get(&server.url, 7, &mut sink).await?;
2047 assert_eq!(sink.0, b"decoded");
2048 }
2049
2050 #[xmtp_common::test(unwrap_try = true)]
2052 async fn unknown_encoding_rejected() {
2053 let server = server(|_| {
2054 Response::builder()
2055 .status(StatusCode::OK)
2056 .header(CONTENT_ENCODING, "br")
2057 .body(Full::new(Bytes::from_static(b"data")))
2058 .unwrap()
2059 })
2060 .await;
2061 let error = allowed()
2062 .get(&server.url, 10, &mut MemorySink::default())
2063 .await
2064 .unwrap_err();
2065 assert_eq!(error.cause, Cause::HttpStatus);
2066 assert_eq!(error.http_status, Some(200));
2067 }
2068
2069 #[xmtp_common::test(unwrap_try = true)]
2071 async fn invalid_gzip_keeps_http_status() {
2072 let server = server(|_| {
2073 Response::builder()
2074 .status(StatusCode::OK)
2075 .header(CONTENT_ENCODING, "gzip")
2076 .body(Full::new(Bytes::from_static(b"not gzip")))
2077 .unwrap()
2078 })
2079 .await;
2080 let error = allowed()
2081 .get(&server.url, 100, &mut MemorySink::default())
2082 .await
2083 .unwrap_err();
2084 assert_eq!(error.cause, Cause::HttpStatus);
2085 assert_eq!(error.http_status, Some(200));
2086 }
2087
2088 #[xmtp_common::test(unwrap_try = true)]
2090 async fn status_mapping() {
2091 for (status, expected) in [
2092 (StatusCode::NOT_FOUND, Cause::NotFound),
2093 (StatusCode::GONE, Cause::NotFound),
2094 (StatusCode::INTERNAL_SERVER_ERROR, Cause::HttpStatus),
2095 ] {
2096 let server = server(move |_| answer(status, "hidden")).await;
2097 let mut sink = MemorySink::default();
2098 assert_eq!(
2099 allowed()
2100 .get(&server.url, 100, &mut sink)
2101 .await
2102 .unwrap_err()
2103 .cause,
2104 expected
2105 );
2106 assert!(sink.0.is_empty());
2107 }
2108 }
2109
2110 #[xmtp_common::test(unwrap_try = true)]
2112 async fn get_http_error_keeps_status() {
2113 let server = server(|_| answer(StatusCode::SERVICE_UNAVAILABLE, "unavailable")).await;
2114 let error = allowed()
2115 .get(&server.url, 100, &mut MemorySink::default())
2116 .await
2117 .unwrap_err();
2118 assert_eq!(error.cause, Cause::HttpStatus);
2119 assert_eq!(error.http_status, Some(503));
2120 }
2121
2122 #[xmtp_common::test(unwrap_try = true)]
2124 async fn get_transport_failure_has_no_status() {
2125 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
2126 let url = format!("http://{}", listener.local_addr()?);
2127 let accepted = tokio::spawn(async move {
2128 let (socket, _) = listener.accept().await.unwrap();
2129 drop(socket);
2130 });
2131 let error = allowed()
2132 .get(&url, 100, &mut MemorySink::default())
2133 .await
2134 .unwrap_err();
2135 tokio::time::timeout(Duration::from_secs(1), accepted).await??;
2136 assert_eq!(error.cause, Cause::Network);
2137 assert_eq!(error.http_status, None);
2138 }
2139
2140 #[xmtp_common::test(unwrap_try = true)]
2141 async fn no_credential_headers() {
2142 use http_body_util::BodyExt;
2143
2144 let seen_get = Arc::new(AtomicBool::new(false));
2145 let seen_put = Arc::new(AtomicBool::new(false));
2146 let get_flag = seen_get.clone();
2147 let put_flag = seen_put.clone();
2148 let server = server_async(move |request| {
2149 let get_flag = get_flag.clone();
2150 let put_flag = put_flag.clone();
2151 async move {
2152 let clean = request.headers().get("authorization").is_none()
2153 && request.headers().get("cookie").is_none()
2154 && request.headers().get("x-xmtp-inbox-id").is_none();
2155 if request.method() == Method::PUT {
2156 let body = request.into_body().collect().await.unwrap().to_bytes();
2157 assert_eq!(body.as_ref(), b"body");
2158 put_flag.store(clean, Ordering::Relaxed);
2159 answer(StatusCode::ACCEPTED, "")
2160 } else {
2161 get_flag.store(
2162 clean && request.headers().get(ACCEPT_ENCODING).unwrap() == "identity",
2163 Ordering::Relaxed,
2164 );
2165 answer(StatusCode::OK, "x")
2166 }
2167 }
2168 })
2169 .await;
2170 allowed()
2171 .get(&server.url, 10, &mut MemorySink::default())
2172 .await?;
2173 assert!(seen_get.load(Ordering::Relaxed));
2174
2175 let directory = tempfile::tempdir()?;
2176 let store = NativeStore::new(directory.path()).await?;
2177 let staged = crate::store::staged_path(&"a".repeat(64))?;
2178 let mut writer = store.create_temp(".tmp/body").await?;
2179 writer.write(b"body").await?;
2180 store.sync(&mut writer).await?;
2181 drop(writer);
2182 store.rename(".tmp/body", &staged).await?;
2183 let upload = UploadRequest {
2184 method: "PUT".into(),
2185 url: server.url.clone(),
2186 headers: vec![],
2187 expires_in_seconds: 60,
2188 };
2189 assert_eq!(
2190 allowed()
2191 .put(&upload, store.open_read(&staged).await?)
2192 .await?,
2193 PutOutcome::Stored
2194 );
2195 assert!(seen_put.load(Ordering::Relaxed));
2196 }
2197
2198 #[xmtp_common::test(unwrap_try = true)]
2200 async fn signed_authorization_header_is_sent() {
2201 use http_body_util::BodyExt;
2202 let signed = "AWS4-HMAC-SHA256 Credential=example";
2203 let (seen_tx, seen_rx) = tokio::sync::oneshot::channel();
2204 let seen_tx = Arc::new(std::sync::Mutex::new(Some(seen_tx)));
2205 let server = server_async(move |request| {
2206 let seen_tx = seen_tx.clone();
2207 async move {
2208 let header = request.headers().get("authorization").unwrap().clone();
2209 let body = request.into_body().collect().await.unwrap().to_bytes();
2210 assert_eq!(body.as_ref(), b"body");
2211 seen_tx
2212 .lock()
2213 .unwrap()
2214 .take()
2215 .unwrap()
2216 .send(header)
2217 .unwrap();
2218 answer(StatusCode::CREATED, "")
2219 }
2220 })
2221 .await;
2222 let (_directory, body) = staged_body()?;
2223 let mut request = upload(server.url.clone());
2224 request
2225 .headers
2226 .push(("authorization".into(), signed.into()));
2227 assert_eq!(allowed().put(&request, body).await?, PutOutcome::Stored);
2228 assert_eq!(seen_rx.await?.to_str()?, signed);
2229 }
2230
2231 #[xmtp_common::test(unwrap_try = true)]
2232 async fn upload_precondition_is_already_stored() {
2233 let server = server(|_| answer(StatusCode::PRECONDITION_FAILED, "")).await;
2234 let directory = tempfile::tempdir()?;
2235 let store = NativeStore::new(directory.path()).await?;
2236 let staged = crate::store::staged_path(&"a".repeat(64))?;
2237 let mut writer = store.create_temp(".tmp/body").await?;
2238 writer.write(b"body").await?;
2239 store.sync(&mut writer).await?;
2240 drop(writer);
2241 store.rename(".tmp/body", &staged).await?;
2242 let upload = UploadRequest {
2243 method: "PUT".into(),
2244 url: server.url.clone(),
2245 headers: vec![],
2246 expires_in_seconds: 60,
2247 };
2248 assert_eq!(
2249 allowed()
2250 .put(&upload, store.open_read(&staged).await?)
2251 .await?,
2252 PutOutcome::AlreadyStored
2253 );
2254 }
2255
2256 fn short_timeout() -> Transfer {
2257 Transfer::with_timeouts(
2258 AttachmentOptions {
2259 allow_private_network: true,
2260 ..Default::default()
2261 },
2262 Duration::from_secs(1),
2263 Duration::from_millis(400),
2264 )
2265 .unwrap()
2266 }
2267
2268 #[xmtp_common::test(unwrap_try = true)]
2270 async fn connect_timeout_is_network() {
2271 let transfer = Transfer::with_resolver_and_timeouts(
2272 AttachmentOptions::default(),
2273 Arc::new(PendingResolver),
2274 Duration::from_millis(100),
2275 Duration::from_millis(400),
2276 )?;
2277 let started = Instant::now();
2278 let error = tokio::time::timeout(
2279 Duration::from_secs(2),
2280 transfer.get(
2281 "https://pending.example/object",
2282 1,
2283 &mut MemorySink::default(),
2284 ),
2285 )
2286 .await?
2287 .unwrap_err();
2288 assert_eq!(error.cause, Cause::Network);
2289 assert!(started.elapsed() < Duration::from_secs(2));
2290
2291 let directory = tempfile::tempdir()?;
2292 let store = NativeStore::new(directory.path()).await?;
2293 let mut writer = store.create_temp(".tmp/body").await?;
2294 writer.write(b"body").await?;
2295 store.sync(&mut writer).await?;
2296 drop(writer);
2297 store.rename(".tmp/body", "body").await?;
2298 let request = UploadRequest {
2299 method: "PUT".into(),
2300 url: "https://pending.example/object".into(),
2301 headers: vec![],
2302 expires_in_seconds: 60,
2303 };
2304 let error = tokio::time::timeout(
2305 Duration::from_secs(2),
2306 transfer.put(&request, store.open_read("body").await?),
2307 )
2308 .await?
2309 .unwrap_err();
2310 assert_eq!(error.cause, Cause::Network);
2311 }
2312
2313 #[xmtp_common::test(unwrap_try = true)]
2315 async fn put_stalls_after_body() {
2316 use http_body_util::BodyExt;
2317 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
2318 let url = format!("http://{}", listener.local_addr()?);
2319 let (body_received, received) = tokio::sync::oneshot::channel();
2320 let task = tokio::spawn(async move {
2321 let (socket, _) = listener.accept().await.unwrap();
2322 let sender = Arc::new(std::sync::Mutex::new(Some(body_received)));
2323 let service = service_fn(move |request: Request<Incoming>| {
2324 let sender = sender.clone();
2325 async move {
2326 let body = request.into_body().collect().await.unwrap().to_bytes();
2327 assert_eq!(&body[..], b"upload body");
2328 sender.lock().unwrap().take().unwrap().send(()).unwrap();
2329 tokio::time::sleep(Duration::from_secs(2)).await;
2330 Ok::<_, std::convert::Infallible>(answer(StatusCode::OK, ""))
2331 }
2332 });
2333 let _ = http1::Builder::new()
2334 .serve_connection(TokioIo::new(socket), service)
2335 .await;
2336 });
2337 let directory = tempfile::tempdir()?;
2338 let store = NativeStore::new(directory.path()).await?;
2339 let mut writer = store.create_temp(".tmp/body").await?;
2340 writer.write(b"upload body").await?;
2341 store.sync(&mut writer).await?;
2342 drop(writer);
2343 store.rename(".tmp/body", "body").await?;
2344 let request = UploadRequest {
2345 method: "PUT".into(),
2346 url,
2347 headers: vec![],
2348 expires_in_seconds: 60,
2349 };
2350 let error = short_timeout()
2351 .put(&request, store.open_read("body").await?)
2352 .await
2353 .unwrap_err();
2354 assert_eq!(error.cause, Cause::Network);
2355 tokio::time::timeout(Duration::from_secs(1), received).await??;
2356 task.abort();
2357 let _ = task.await;
2358 }
2359
2360 #[xmtp_common::test(unwrap_try = true)]
2362 async fn put_2xx_does_not_wait_for_response_body() {
2363 use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
2364
2365 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
2366 let url = format!("http://{}", listener.local_addr()?);
2367 let (received_tx, received_rx) = tokio::sync::oneshot::channel();
2368 let task = tokio::spawn(async move {
2369 let (socket, _) = listener.accept().await.unwrap();
2370 let mut reader = BufReader::new(socket);
2371 let mut line = String::new();
2372 let mut content_length = None;
2373 loop {
2374 line.clear();
2375 assert!(reader.read_line(&mut line).await.unwrap() > 0);
2376 if line == "\r\n" {
2377 break;
2378 }
2379 if let Some(length) = line.to_ascii_lowercase().strip_prefix("content-length: ") {
2380 content_length = Some(length.trim().parse::<usize>().unwrap());
2381 }
2382 }
2383 let mut body = vec![0; content_length.unwrap()];
2384 reader.read_exact(&mut body).await.unwrap();
2385 assert_eq!(body, b"body");
2386 received_tx.send(()).unwrap();
2387 reader
2388 .get_mut()
2389 .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\n")
2390 .await
2391 .unwrap();
2392 tokio::time::sleep(Duration::from_secs(2)).await;
2393 });
2394 let (_directory, body) = staged_body()?;
2395 let outcome = tokio::time::timeout(
2396 Duration::from_secs(1),
2397 short_timeout().put(&upload(url), body),
2398 )
2399 .await??;
2400 assert_eq!(outcome, PutOutcome::Stored);
2401 tokio::time::timeout(Duration::from_secs(1), received_rx).await??;
2402 task.abort();
2403 let _ = task.await;
2404 }
2405
2406 #[xmtp_common::test(unwrap_try = true)]
2408 async fn get_stalls_after_bytes() {
2409 use tokio::io::{AsyncReadExt, AsyncWriteExt};
2410 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
2411 let url = format!("http://{}", listener.local_addr()?);
2412 let task = tokio::spawn(async move {
2413 let (mut socket, _) = listener.accept().await.unwrap();
2414 let mut request = [0_u8; 1024];
2415 let _ = socket.read(&mut request).await;
2416 socket
2417 .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\na")
2418 .await
2419 .unwrap();
2420 tokio::time::sleep(Duration::from_secs(2)).await;
2421 });
2422 let mut sink = MemorySink::default();
2423 let error = short_timeout().get(&url, 5, &mut sink).await.unwrap_err();
2424 assert_eq!(error.cause, Cause::Network);
2425 assert_eq!(sink.0, b"a");
2426 task.abort();
2427 let _ = task.await;
2428 }
2429
2430 #[xmtp_common::test(unwrap_try = true)]
2432 async fn get_trickle_has_no_total_deadline() {
2433 use tokio::io::{AsyncReadExt, AsyncWriteExt};
2434 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
2435 let url = format!("http://{}", listener.local_addr()?);
2436 let task = tokio::spawn(async move {
2437 let (mut socket, _) = listener.accept().await.unwrap();
2438 let mut request = [0_u8; 1024];
2439 let _ = socket.read(&mut request).await;
2440 socket
2441 .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n")
2442 .await
2443 .unwrap();
2444 for byte in b"abcde" {
2445 tokio::time::sleep(Duration::from_millis(150)).await;
2446 socket.write_all(&[*byte]).await.unwrap();
2447 }
2448 });
2449 let mut sink = MemorySink::default();
2450 short_timeout().get(&url, 5, &mut sink).await?;
2451 assert_eq!(sink.0, b"abcde");
2452 task.await?;
2453 }
2454
2455 #[xmtp_common::test(unwrap_try = true)]
2457 async fn put_trickle_has_no_total_deadline() {
2458 use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
2459 const BODY_SIZE: usize = 3 * 1024 * 1024;
2460 let socket = tokio::net::TcpSocket::new_v4()?;
2461 socket.set_recv_buffer_size(8192)?;
2462 socket.bind("127.0.0.1:0".parse()?)?;
2463 let listener = socket.listen(1)?;
2464 let url = format!("http://{}", listener.local_addr()?);
2465 let task = tokio::spawn(async move {
2466 let (socket, _) = listener.accept().await.unwrap();
2467 let mut reader = BufReader::new(socket);
2468 let mut line = String::new();
2469 let mut content_length = None;
2470 loop {
2471 line.clear();
2472 assert!(reader.read_line(&mut line).await.unwrap() > 0);
2473 if line == "\r\n" {
2474 break;
2475 }
2476 let lower = line.to_ascii_lowercase();
2477 if let Some(length) = lower.strip_prefix("content-length: ") {
2478 content_length = Some(length.trim().parse::<usize>().unwrap());
2479 }
2480 }
2481 assert_eq!(content_length, Some(BODY_SIZE));
2482 let mut remaining = BODY_SIZE;
2483 let mut tail_started = false;
2484 let mut buffer = [0_u8; 8192];
2485 while remaining > 0 {
2486 if remaining <= 512 * 1024 {
2487 if !tail_started {
2488 tokio::time::sleep(Duration::from_millis(500)).await;
2489 tail_started = true;
2490 }
2491 tokio::time::sleep(Duration::from_millis(10)).await;
2492 }
2493 let limit = remaining.min(buffer.len());
2494 let size = reader.read(&mut buffer[..limit]).await.unwrap();
2495 assert!(size > 0);
2496 assert!(buffer[..size].iter().all(|byte| *byte == 0x5a));
2497 remaining -= size;
2498 }
2499 reader
2500 .get_mut()
2501 .write_all(b"HTTP/1.1 202 Accepted\r\nContent-Length: 0\r\n\r\n")
2502 .await
2503 .unwrap();
2504 });
2505 let directory = tempfile::tempdir()?;
2506 let store = NativeStore::new(directory.path()).await?;
2507 let mut writer = store.create_temp(".tmp/body").await?;
2508 for _ in 0..BODY_SIZE / CHUNK_SIZE {
2509 writer.write(&[0x5a; CHUNK_SIZE]).await?;
2510 }
2511 store.sync(&mut writer).await?;
2512 drop(writer);
2513 store.rename(".tmp/body", "body").await?;
2514 let request = UploadRequest {
2515 method: "PUT".into(),
2516 url,
2517 headers: vec![],
2518 expires_in_seconds: 60,
2519 };
2520 let started = Instant::now();
2521 let transfer = Transfer::with_timeouts(
2522 AttachmentOptions {
2523 allow_private_network: true,
2524 ..Default::default()
2525 },
2526 Duration::from_secs(1),
2527 Duration::from_secs(1),
2528 )?;
2529 assert_eq!(
2530 transfer
2531 .put(&request, store.open_read("body").await?)
2532 .await?,
2533 PutOutcome::Stored
2534 );
2535 assert!(started.elapsed() > Duration::from_secs(1));
2536 task.await?;
2537 }
2538}