Skip to main content

xmtp_attachments/http/
native.rs

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    // Allow normal DEFLATE block overhead and framing, but stop streams that
37    // consume input without producing decoded bytes.
38    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
188/// Native HTTP transfer. Downloads check each connected address.
189pub 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            // BufReader keeps bytes after the zlib end marker available here.
385            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    // Aborting the producer drops input_tx and consumed_rx. This lets the
449    // blocking decoder stop when the caller drops the download future.
450    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    // verifies: ATCH-053
611    #[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    // verifies: ATCH-053
621    #[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    // verifies: ATCH-024
705    #[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        // Start the second PUT only after the first server has read body bytes.
940        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    // verifies: ATCH-071
1072    #[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    // verifies: ATCH-071
1096    #[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    // verifies: ATCH-071
1115    #[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    // verifies: ATCH-071
1140    #[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    // verifies: ATCH-025
1235    #[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    // verifies: ATCH-025
1244    #[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    // verifies: ATCH-079
1256    #[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    // verifies: ATCH-025
1264    #[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    // verifies: ATCH-025
1342    #[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    // verifies: ATCH-054
1442    #[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    // verifies: ATCH-054
1504    #[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    // verifies: ATCH-054
1529    #[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    // verifies: ATCH-055
1544    #[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        // The permitted loopback source cannot make an insecure target safe.
1559        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    // verifies: ATCH-055
1593    #[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    // verifies: ATCH-055
1621    #[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    // verifies: ATCH-056
1649    #[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    // verifies: ATCH-056
1671    #[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            // Each non-final stored block has no decoded bytes.
1692            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    // verifies: ATCH-056
1713    #[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    // verifies: ATCH-056
2051    #[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    // verifies: ATCH-079
2070    #[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    // verifies: ATCH-057
2089    #[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    // verifies: ATCH-079
2111    #[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    // verifies: ATCH-079
2123    #[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    // verifies: ATCH-024
2199    #[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    // verifies: ATCH-070
2269    #[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    // verifies: ATCH-070
2314    #[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    // verifies: ATCH-025
2361    #[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    // verifies: ATCH-070
2407    #[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    // verifies: ATCH-070
2431    #[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    // verifies: ATCH-070
2456    #[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}