use crate::api::Error;
const MAX_REDIRECTS: usize = 5;
pub fn http_get(url: &str) -> Result<Vec<u8>, Error> {
let mut current = url.to_owned();
for _ in 0..=MAX_REDIRECTS {
let resp = crate::net::http_get_once(¤t, &[("Accept", "*/*")], None)
.map_err(|e| Error::vm_msg(format!("ADD url: {e}")))?;
match resp.status {
200..=299 => return Ok(resp.body),
301 | 302 | 303 | 307 | 308 => {
let loc = crate::net::header_value(&resp.headers, "location").ok_or_else(|| {
Error::vm_msg(format!(
"ADD url: HTTP {} redirect without Location",
resp.status
))
})?;
current = crate::net::resolve_redirect(¤t, loc);
}
other => {
return Err(Error::vm_msg(format!(
"ADD url: GET {current} -> HTTP {other}"
)))
}
}
}
Err(Error::vm_msg(format!(
"ADD url: too many redirects (>{MAX_REDIRECTS}) for {url}"
)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn https_get_smoke() {
match http_get("https://example.com/") {
Ok(body) => {
let text = String::from_utf8_lossy(&body);
assert!(
text.contains("Example Domain"),
"unexpected body from https://example.com/:\n{}",
&text[..text.len().min(256)]
);
}
Err(e) => eprintln!("[skip] no network for HTTPS smoke: {e:?}"),
}
}
#[test]
fn redirect_loop_capped() {
use std::io::{Read as _, Write as _};
std::env::set_var("SUPERMACHINE_BUILD_ALLOW_PRIVATE_FETCH", "1");
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
std::thread::spawn(move || {
for _ in 0..(MAX_REDIRECTS + 4) {
match listener.accept() {
Ok((mut s, _)) => {
let mut buf = [0u8; 1024];
let _ = s.read(&mut buf);
let _ = s.write_all(
b"HTTP/1.1 302 Found\r\nLocation: /again\r\n\
Content-Length: 0\r\nConnection: close\r\n\r\n",
);
}
Err(_) => break,
}
}
});
let url = format!("http://127.0.0.1:{port}/start");
let err = http_get(&url).unwrap_err();
let msg = format!("{err:?}");
assert!(msg.contains("too many redirects"), "got: {msg}");
}
}