Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ic-asset-certification): remove dependency on regex to reduce WASM size #391

Merged
merged 3 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ http = "0.2"
flate2 = "1.0"
sha2 = "0.10"
urlencoding = "2.1"
regex = "1"
rstest = "0.18"
rstest_reuse = "0.2"
tokio = { version = "1.35", features = ["full"] }
Expand Down
1 change: 0 additions & 1 deletion packages/ic-asset-certification/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ ic-certification.workspace = true
ic-http-certification.workspace = true
thiserror.workspace = true
globset = "0.4"
regex.workspace = true

[dev-dependencies]
rand_chacha.workspace = true
Expand Down
76 changes: 53 additions & 23 deletions packages/ic-asset-certification/src/asset_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use ic_http_certification::{
HttpCertification, HttpCertificationPath, HttpCertificationTree, HttpCertificationTreeEntry,
HttpRequest, HttpResponse, CERTIFICATE_EXPRESSION_HEADER_NAME,
};
use regex::Regex;
use std::{borrow::Cow, cell::RefCell, cmp, collections::HashMap, rc::Rc};

/// A router for certifying and serving static [Assets](Asset).
Expand Down Expand Up @@ -132,21 +131,35 @@ fn encoding_str(maybe_encoding: Option<AssetEncoding>) -> Option<String> {
maybe_encoding.map(|enc| enc.to_string())
}

fn parse_range_header_str(str_value: &str) -> Result<RangeRequestValues, String> {
// expected format: `bytes <range-begin>-[<range-end>]`
let re = Regex::new(r"bytes=(\d+)-(\d*)").expect("internal: wrong RE");
let Some(caps) = re.captures(str_value) else {
return Err("malformed Range header".to_string());
};
let range_begin: usize = caps
.get(1)
.ok_or_else(|| "missing range-begin".to_string())?
.as_str()
.parse()
.map_err(|_| "malformed range-begin".to_string())?;
let range_end: Option<usize> = caps.get(2).and_then(|v| v.as_str().parse().ok());

// TODO: add sanity checks for the parsed values
fn parse_range_header_str(range_str: &str) -> Result<RangeRequestValues, String> {
// expected format: `bytes=<range-begin>-[<range-end>]`
let str_value = range_str.trim();
if !str_value.starts_with("bytes=") {
return Err(format!("Invalid Range header '{}'", range_str).to_string());
}
let str_value = str_value.trim_start_matches("bytes=");
let range_header_parts = str_value.split('-').collect::<Vec<_>>();
if range_header_parts.is_empty() || range_header_parts.len() != 2 {
return Err(format!("Invalid Range header '{}'", range_str).to_string());
}

let range_begin = range_header_parts[0].parse::<usize>().map_err(|e| {
format!(
"Malformed range_begin in Range header '{}': {}",
range_str, e
)
})?;
let range_end =
match range_header_parts[1] {
"" => None,
_ => Some(range_header_parts[1].parse::<usize>().map_err(|e| {
format!("Malformed range_end in Range header '{}': {}", range_str, e)
})?),
};

if range_begin > range_end.unwrap_or(usize::MAX) {
return Err(format!("Invalid values in Range header '{}'", range_str).to_string());
}
Ok(RangeRequestValues {
range_begin,
range_end,
Expand Down Expand Up @@ -1016,7 +1029,7 @@ mod tests {
format!("bytes={}-", range_begin)
};
let result = parse_range_header_str(&input);
let output = result.unwrap_or_else(|_| panic!("failed parsing '{input}'"));
let output = result.unwrap_or_else(|e| panic!("failed parsing '{input}': {:?}", e));
assert_eq!(
RangeRequestValues {
range_begin,
Expand All @@ -1027,15 +1040,32 @@ mod tests {
}

#[rstest]
#[case("byte 1-2/3")]
#[case("bites 2-4")]
#[case("bytes 100-end")]
#[case("bytes 12345")]
#[case("")]
#[case("byte=1-2")]
#[case("bites=2-4")]
#[case("bytes 7-11")]
#[case("bytes=12345")]
#[case("something else")]
#[case("bytes dead-beef")]
#[case("bytes=-5-19")]
fn should_fail_parse_range_header_str_on_invalid_input(#[case] malformed_input: &str) {
let result = parse_range_header_str(malformed_input);
assert_matches!(result, Err(e) if e.to_string().contains("Invalid Range header"));
}

#[rstest]
#[case("bytes=100-end")]
#[case("bytes=dead-beef")]
fn should_fail_parse_range_header_str_on_malformed_input(#[case] malformed_input: &str) {
let result = parse_range_header_str(malformed_input);
assert_matches!(result, Err(e) if e.to_string().contains("malformed Range header"));
assert_matches!(result, Err(e) if e.to_string().contains("Malformed range_"));
}

#[rstest]
#[case("bytes=100-20")]
#[case("bytes=20-19")]
fn should_fail_parse_range_header_str_on_invalid_values(#[case] malformed_input: &str) {
let result = parse_range_header_str(malformed_input);
assert_matches!(result, Err(e) if e.to_string().contains("Invalid values in Range header"));
}

#[rstest]
Expand Down
Loading