2022-05-29 19:41:22 +00:00
|
|
|
use anyhow::{anyhow, Context, Result};
|
2023-06-26 10:06:24 +00:00
|
|
|
use headers::{HeaderMap, HeaderName};
|
2022-06-02 16:53:04 +00:00
|
|
|
use lychee_lib::{remap::Remaps, Base};
|
feat: Add support for ranges in the `--accept` option / config field (#1167)
Adds support for accept ranges discussed in #1157. This allows the user to specify custom HTTP status codes accepted during checking and thus will report as valid (not broken). The accept option only supports specifying status codes as a comma-separated list. With this PR, the option will accept a list of status code ranges formatted like this:
```toml
accept = ["100..=103", "200..=299", "403"]
```
These combinations will be supported: `..<end>`, ` ..=<end>`, `<start>..<end>` and `<start>..=<end>`.
The behavior is copied from the Rust Range like concepts:
```
..<end>, includes 0 to <end> (exclusive)
..=<end>, includes 0 to <end> (inclusive)
<start>..<end>, includes <start> to <end> (exclusive)
<start>..=<end>, includes <start> to <end> (inclusive)
```
- Foundation and enhancements for accept ranges, including support for comma-separated strings and integration into the CLI.
- Implementations and updates for AcceptSelector, including Default, Display, and serde defaults.
- Address and fix various errors: clippy, cargo fmt, and tests.
- Add more tests, address edge cases, and enhance error messaging, especially for TOML config parsing.
- Update dependencies.
2023-09-17 19:39:01 +00:00
|
|
|
use std::time::Duration;
|
2021-10-04 23:37:43 +00:00
|
|
|
|
2022-05-29 19:41:22 +00:00
|
|
|
/// Split a single HTTP header into a (key, value) tuple
|
2025-03-20 21:48:50 +00:00
|
|
|
fn read_header(input: &str) -> Result<(String, String), anyhow::Error> {
|
|
|
|
|
if let Some((key, value)) = input.split_once('=') {
|
|
|
|
|
Ok((key.to_string(), value.to_string()))
|
|
|
|
|
} else {
|
|
|
|
|
Err(anyhow!(
|
2022-05-29 19:41:22 +00:00
|
|
|
"Header value must be of the form key=value, got {}",
|
2021-10-04 23:37:43 +00:00
|
|
|
input
|
2025-03-20 21:48:50 +00:00
|
|
|
))
|
2021-10-04 23:37:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-29 19:41:22 +00:00
|
|
|
/// Parse seconds into a `Duration`
|
2022-02-24 11:24:57 +00:00
|
|
|
pub(crate) const fn parse_duration_secs(secs: usize) -> Duration {
|
|
|
|
|
Duration::from_secs(secs as u64)
|
2021-10-04 23:37:43 +00:00
|
|
|
}
|
|
|
|
|
|
2022-05-29 19:41:22 +00:00
|
|
|
/// Parse HTTP headers into a `HeaderMap`
|
2021-10-04 23:37:43 +00:00
|
|
|
pub(crate) fn parse_headers<T: AsRef<str>>(headers: &[T]) -> Result<HeaderMap> {
|
|
|
|
|
let mut out = HeaderMap::new();
|
|
|
|
|
for header in headers {
|
|
|
|
|
let (key, val) = read_header(header.as_ref())?;
|
2021-12-01 17:25:11 +00:00
|
|
|
out.insert(HeaderName::from_bytes(key.as_bytes())?, val.parse()?);
|
2021-10-04 23:37:43 +00:00
|
|
|
}
|
|
|
|
|
Ok(out)
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-29 19:41:22 +00:00
|
|
|
/// Parse URI remaps
|
|
|
|
|
pub(crate) fn parse_remaps(remaps: &[String]) -> Result<Remaps> {
|
|
|
|
|
Remaps::try_from(remaps)
|
|
|
|
|
.context("Remaps must be of the form '<pattern> <uri>' (separated by whitespace)")
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-02 16:53:04 +00:00
|
|
|
pub(crate) fn parse_base(src: &str) -> Result<Base, lychee_lib::ErrorKind> {
|
|
|
|
|
Base::try_from(src)
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-04 23:37:43 +00:00
|
|
|
#[cfg(test)]
|
2022-06-03 18:13:07 +00:00
|
|
|
mod tests {
|
2021-10-04 23:37:43 +00:00
|
|
|
|
2023-06-26 10:06:24 +00:00
|
|
|
use headers::HeaderMap;
|
2022-05-29 19:41:22 +00:00
|
|
|
use regex::Regex;
|
2023-07-05 13:05:19 +00:00
|
|
|
use reqwest::header;
|
2021-10-04 23:37:43 +00:00
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_parse_custom_headers() {
|
|
|
|
|
let mut custom = HeaderMap::new();
|
|
|
|
|
custom.insert(header::ACCEPT, "text/html".parse().unwrap());
|
|
|
|
|
assert_eq!(parse_headers(&["accept=text/html"]).unwrap(), custom);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 21:48:50 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_parse_custom_headers_with_equals() {
|
|
|
|
|
let mut custom_with_equals = HeaderMap::new();
|
|
|
|
|
custom_with_equals.insert("x-test", "check=this".parse().unwrap());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
parse_headers(&["x-test=check=this"]).unwrap(),
|
|
|
|
|
custom_with_equals
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-29 19:41:22 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_parse_remap() {
|
|
|
|
|
let remaps =
|
|
|
|
|
parse_remaps(&["https://example.com http://127.0.0.1:8080".to_string()]).unwrap();
|
|
|
|
|
assert_eq!(remaps.len(), 1);
|
|
|
|
|
let (pattern, url) = remaps[0].to_owned();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
pattern.to_string(),
|
|
|
|
|
Regex::new("https://example.com").unwrap().to_string()
|
|
|
|
|
);
|
2023-07-05 13:05:19 +00:00
|
|
|
assert_eq!(url, "http://127.0.0.1:8080");
|
2022-05-29 19:41:22 +00:00
|
|
|
}
|
2021-10-04 23:37:43 +00:00
|
|
|
}
|