use std::fs::File;
use std::io::prelude::*;
use std::io::{Error, ErrorKind};
use url::Url;
#[derive(Debug, Copy, Clone)]
pub enum ResourceAccess {
LocalOnly,
RemoteAllowed,
}
impl ResourceAccess {
pub fn permits(self, url: &Url) -> bool {
match self {
ResourceAccess::LocalOnly if is_local(url) => true,
ResourceAccess::RemoteAllowed => true,
_ => false,
}
}
}
fn is_local(url: &Url) -> bool {
url.scheme() == "file" && url.to_file_path().is_ok()
}
#[cfg(feature = "reqwest")]
fn fetch_http(url: &Url) -> Result<Vec<u8>, failure::Error> {
let mut response = reqwest::blocking::get(url.clone())?;
if response.status().is_success() {
let mut buffer = Vec::new();
response.read_to_end(&mut buffer)?;
Ok(buffer)
} else {
Err(Error::new(
ErrorKind::Other,
format!("HTTP error status {} by GET {}", response.status(), url),
)
.into())
}
}
#[cfg(not(feature = "reqwest"))]
fn fetch_http(url: &Url) -> Result<Vec<u8>, failure::Error> {
let output = std::process::Command::new("curl")
.arg("-fsSL")
.arg(url.to_string())
.output()?;
if output.status.success() {
Ok(output.stdout)
} else {
Err(Error::new(
ErrorKind::Other,
format!(
"curl {} failed: {}",
url,
String::from_utf8_lossy(&output.stderr)
),
)
.into())
}
}
pub fn read_url(url: &Url) -> Result<Vec<u8>, failure::Error> {
match url.scheme() {
"file" => match url.to_file_path() {
Ok(path) => {
let mut buffer = Vec::new();
File::open(path)?.read_to_end(&mut buffer)?;
Ok(buffer)
}
Err(_) => Err(Error::new(
ErrorKind::InvalidInput,
format!("Remote file: URL {} not supported", url),
)
.into()),
},
"http" | "https" => fetch_http(url),
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Protocol of URL {} not supported", url),
)
.into()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
#[cfg(unix)]
fn resource_access_permits_local_resource() {
let resource = Url::parse("file:///foo/bar").unwrap();
assert!(ResourceAccess::LocalOnly.permits(&resource));
assert!(ResourceAccess::RemoteAllowed.permits(&resource));
}
#[test]
#[cfg(unix)]
fn resource_access_permits_remote_file_url() {
let resource = Url::parse("file://example.com/foo/bar").unwrap();
assert!(!ResourceAccess::LocalOnly.permits(&resource));
assert!(ResourceAccess::RemoteAllowed.permits(&resource));
}
#[test]
fn resource_access_permits_https_url() {
let resource = Url::parse("https:///foo/bar").unwrap();
assert!(!ResourceAccess::LocalOnly.permits(&resource));
assert!(ResourceAccess::RemoteAllowed.permits(&resource));
}
#[test]
fn read_url_with_http_url_fails_when_status_404() {
let url = "https://eu.httpbin.org/status/404"
.parse::<url::Url>()
.unwrap();
let result = read_url(&url);
assert!(result.is_err(), "Unexpected success: {:?}", result);
let error = result.unwrap_err().to_string();
if cfg!(feature = "reqwest") {
assert_eq!(
error,
"HTTP error status 404 Not Found by GET https://eu.httpbin.org/status/404"
)
} else {
assert!(
error.contains("curl https://eu.httpbin.org/status/404 failed:"),
"Error did not contain expected string: {}",
error
);
assert!(
error.contains("404 NOT FOUND"),
"Error did not contain expected string: {}",
error
);
}
}
#[test]
fn read_url_with_http_url_returns_content_when_status_200() {
let url = "https://eu.httpbin.org/bytes/100"
.parse::<url::Url>()
.unwrap();
let result = read_url(&url);
assert!(result.is_ok(), "Unexpected error: {:?}", result);
assert_eq!(result.unwrap().len(), 100);
}
}