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

Cleanup, lint, fmt, dev dep update #4

Merged
merged 1 commit into from
Jul 21, 2023
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
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "http-range-header"
version = "0.3.0"
version = "0.3.1"
edition = "2018"
license = "MIT"
readme = "./README.md"
Expand All @@ -10,17 +10,18 @@ homepage = "https://github.com/MarcusGrass/parse-range-headers"
categories = ["parser-implementations", "network-programming", "web-programming"]
keywords = ["http", "parser", "http-headers", "headers", "range"]
exclude = ["/.github", "CONTRIBUTING.md"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
with_error_cause = []

[dependencies]

[dev-dependencies]
criterion = "0.3.5"
criterion = "0.5.1"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
regex = "1.5.4"
regex = "1.8.3"

[[bench]]
name = "benchmark"
Expand Down
52 changes: 42 additions & 10 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
# 0.1.0
Released first version under parse_range_headers
<!-- markdownlint-disable blanks-around-headings blanks-around-lists no-duplicate-heading -->

# 0.2.0
Rename to http-range-header
# Changelog

# 0.2.1
Make some optimization
All notable changes to this project will be documented in this file.

# 0.2.2
Fix a bug where single reversed range headers were erroneously passing validation
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# 0.3.0
Only expose a single error-type to make usage more ergonomic
<!-- next-header -->
## [Unreleased] - ReleaseDate
### Added
### Changed
### Fixed

## [0.3.1] - 2023-07-21

### Fixed
- Now accepts ranges that are out of bounds, but truncates them down to an in-range
value, according to the spec, thanks @jfaust!
- Clean up with clippy pedantic, update docs, format, etc. Resulted in a bench improvement of almost
5%.

## [0.3.0] - 2021-11-25

### Changed

- Only expose a single error-type to make usage more ergonomic

## [0.2.1] - 2021-11-25

### Added

- Make some optimizations

## [0.2.0] - 2021-11-25

### Changed

- Rename to http-range-header

## [0.1.0] - 2021-11-25

### Added

- Released first version under parse_range_headers
54 changes: 29 additions & 25 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
#![warn(clippy::pedantic)]
use core::fmt::{Debug, Display, Formatter};
use core::ops::RangeInclusive;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::ops::RangeInclusive;

#[macro_use]
mod macros;

const UNIT_SEP: &str = "bytes=";
const COMMA: char = ',';
/// Function that parses the content of a range header.
///
/// Follows the spec here https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
/// Follows the [spec here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range)
///
/// And here https://www.ietf.org/rfc/rfc2616.txt
/// And [here](https://www.ietf.org/rfc/rfc2616.txt)
///
/// Will only accept bytes ranges, will update when https://www.iana.org/assignments/http-parameters/http-parameters.xhtml changes to allow other units.
/// Will only accept bytes ranges, will update when [this spec](https://www.iana.org/assignments/http-parameters/http-parameters.xhtml) changes to allow other units.
///
/// Parses ranges strictly, as in the examples contained in the above specifications.
///
Expand Down Expand Up @@ -80,11 +83,11 @@ mod macros;
/// }
/// ```
///
/// The parser makes two passes, one without a known file_size, ensuring all ranges are syntactically correct.
/// The parser makes two passes, one without a known file-size, ensuring all ranges are syntactically correct.
/// The returned struct will through its `validate` method accept a file-size and figure out whether or not the
/// syntactically correct ranges actually makes sense in context
///
/// The range `bytes=0-20` on a file with 15 bytes will be accepted in the first pass as the content_size is unknown.
/// The range `bytes=0-20` on a file with 15 bytes will be accepted in the first pass as the content size is unknown.
/// On the second pass (`validate`) it will be truncated to `file_size - 1` as per [the spec](https://httpwg.org/specs/rfc9110.html#rfc.section.14.1.2).
/// # Example range truncates in `validate` because it exceedes
/// ```
Expand All @@ -100,7 +103,7 @@ mod macros;
/// Range reversal and overlap is also checked in the second pass, the range `bytes=0-20, 5-10`
/// will become two syntactically correct ranges, but `validate` will return ann `Err`.
///
/// This is an opinionated implementation, the spec https://datatracker.ietf.org/doc/html/rfc7233
/// This is an opinionated implementation, [the spec](https://datatracker.ietf.org/doc/html/rfc7233)
/// allows a server to determine its implementation of overlapping ranges, this api currently does not allow it.
///
/// # Example multipart-range fails `validate` because of an overlap
Expand All @@ -114,12 +117,9 @@ mod macros;
/// // Some ranges overlap, all valid ranges get truncated to 1 Err
/// assert!(validated.is_err());
/// ```
///
///

const UNIT_SEP: &str = "bytes=";
const COMMA: char = ',';

/// # Errors
/// Will return an error if the `range_header_value` cannot be strictly parsed into a range
/// per the http spec.
pub fn parse_range_header(
range_header_value: &str,
) -> Result<ParsedRanges, RangeUnsatisfiableError> {
Expand Down Expand Up @@ -166,7 +166,7 @@ pub fn parse_range_header(
}
}

fn trim<'a>(s: &'a str) -> Option<&'a str> {
fn trim(s: &str) -> Option<&str> {
if s.ends_with(char::is_whitespace) || s.match_indices(char::is_whitespace).count() > 1 {
None
} else {
Expand All @@ -181,12 +181,11 @@ fn parse_inner(range: &str) -> Result<SyntacticallyCorrectRange, RangeUnsatisfia
if let Some(end) = strict_parse_u64(end) {
if end == 0 {
return invalid!(format!("Range: {} is not satisfiable, suffixed number of bytes to retrieve is zero.", range));
} else {
return Ok(SyntacticallyCorrectRange::new(
StartPosition::FromLast(end),
EndPosition::LastByte,
));
}
return Ok(SyntacticallyCorrectRange::new(
StartPosition::FromLast(end),
EndPosition::LastByte,
));
}
return invalid!(format!(
"Range: {} is not acceptable, end of range not parseable.",
Expand Down Expand Up @@ -223,8 +222,8 @@ fn parse_inner(range: &str) -> Result<SyntacticallyCorrectRange, RangeUnsatisfia
}

fn strict_parse_u64(s: &str) -> Option<u64> {
if !s.starts_with("+") && (s.len() == 1 || !s.starts_with("0")) {
return u64::from_str_radix(s, 10).ok();
if !s.starts_with('+') && (s.len() == 1 || !s.starts_with('0')) {
return s.parse::<u64>().ok();
}
None
}
Expand All @@ -239,7 +238,7 @@ fn split_exactly_once<'a>(s: &'a str, pat: &'a str) -> Option<(&'a str, &'a str)
Some((left, right))
}

fn split_exactly_once_ch<'a>(s: &'a str, pat: char) -> Option<(&'a str, &'a str)> {
fn split_exactly_once_ch(s: &str, pat: char) -> Option<(&str, &str)> {
let mut iter = s.split(pat);
let left = iter.next()?;
let right = iter.next()?;
Expand All @@ -258,6 +257,9 @@ impl ParsedRanges {
ParsedRanges { ranges }
}

/// Validates a parsed range for a given file-size in bytes.
/// # Errors
/// If the range is invalid for the the file-size.
pub fn validate(
&self,
file_size_bytes: u64,
Expand All @@ -277,13 +279,15 @@ impl ParsedRanges {
}
};
let end = match parsed.end {
EndPosition::Index(i) => std::cmp::min(i, file_size_bytes - 1),
EndPosition::Index(i) => core::cmp::min(i, file_size_bytes - 1),
EndPosition::LastByte => file_size_bytes - 1,
};

let valid = RangeInclusive::new(start, end);
validated.push(valid);
}
// False positive
#[allow(clippy::match_same_arms)]
match validate_ranges(validated.as_slice()) {
RangeValidationResult::Valid => Ok(validated),
RangeValidationResult::Overlapping => invalid!("Ranges overlap".to_string()),
Expand All @@ -310,7 +314,7 @@ impl RangeUnsatisfiableError {
pub struct RangeUnsatisfiableError;

impl Display for RangeUnsatisfiableError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
#[cfg(feature = "with_error_cause")]
{
f.write_str(&self.msg)
Expand Down
Loading