1use fidl_fuchsia_net_http::{self as http, Header};
6use fuchsia_async as fasync;
7use fuchsia_component::client::connect_to_protocol;
8use futures::AsyncReadExt as _;
9use log::debug;
10
11pub mod errors;
12use errors::FetchUrlError;
13
14const HTTP_PARTIAL_CONTENT_OK: u32 = 206;
15const HTTP_OK: u32 = 200;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct Range {
20 pub start: u64,
22 pub end: Option<u64>,
24}
25
26pub async fn fetch_url(
27 url: impl Into<String>,
28 range: Option<Range>,
29 mut headers: Vec<Header>,
30) -> Result<Vec<u8>, FetchUrlError> {
31 let http_svc = connect_to_protocol::<http::LoaderMarker>()
32 .map_err(FetchUrlError::FidlHttpServiceConnectionError)?;
33
34 let url_string = url.into();
35
36 if let Some(r) = &range {
37 const RANGE_HEADER_NAME: &[u8] = b"range";
38 if headers.iter().any(|h| h.name.eq_ignore_ascii_case(RANGE_HEADER_NAME)) {
39 return Err(FetchUrlError::DuplicateRangeHeader);
40 }
41
42 let range_string = if let Some(end) = r.end {
43 format!("bytes={}-{}", r.start, end)
44 } else {
45 format!("bytes={}-", r.start)
46 };
47 headers.push(Header { name: RANGE_HEADER_NAME.into(), value: range_string.into() });
48 }
49
50 let url_request = http::Request {
51 url: Some(url_string),
52 method: Some(String::from("GET")),
53 headers: if headers.is_empty() { None } else { Some(headers) },
54 body: None,
55 deadline: None,
56 ..Default::default()
57 };
58
59 let response = http_svc.fetch(url_request).await.map_err(FetchUrlError::LoaderFIDLError)?;
60
61 debug!("got HTTP status {:?} for final URL {:?}", response.status_code, response.final_url);
62
63 if let Some(e) = response.error {
64 return Err(FetchUrlError::LoaderFetchError(e));
65 }
66
67 let zx_socket = response.body.ok_or(FetchUrlError::UrlReadBodyError)?;
68 let mut socket = fasync::Socket::from_socket(zx_socket);
69
70 if let Some(range) = range {
71 match response.status_code {
72 Some(HTTP_PARTIAL_CONTENT_OK) => {
73 let mut body = Vec::new();
74 let bytes_received = socket
75 .read_to_end(&mut body)
76 .await
77 .map_err(FetchUrlError::ReadFromSocketError)?
78 as u64;
79 let start = range.start;
80 if let Some(end) = range.end {
81 let expected = end - start + 1;
82 if bytes_received != expected {
83 return Err(FetchUrlError::SizeReadMismatch(bytes_received, expected));
84 }
85 }
86 debug!(
87 "successfully fetched partial content starting from {}, {} bytes total",
88 start,
89 body.len()
90 );
91 Ok(body)
92 }
93 Some(code) => Err(FetchUrlError::UnexpectedHttpStatusCode(code)),
94 None => Err(FetchUrlError::NoStatusResponse),
95 }
96 } else {
97 match response.status_code {
98 Some(HTTP_OK | HTTP_PARTIAL_CONTENT_OK) => {
99 let mut body = Vec::new();
100 let bytes_received = socket
101 .read_to_end(&mut body)
102 .await
103 .map_err(FetchUrlError::ReadFromSocketError)?;
104 debug!("successfully fetched content, {} bytes total", bytes_received);
105 Ok(body)
106 }
107 Some(code) => Err(FetchUrlError::UnexpectedHttpStatusCode(code)),
108 None => Err(FetchUrlError::NoStatusResponse),
109 }
110 }
111}