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

feat: add strict version of uri parsing #689

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/uri/mod.rs
Expand Up @@ -339,6 +339,57 @@ impl Uri {
parse_full(s)
}

/// Similar to [`from_shared`] but does not accept `{`, `}` and `"`
fn from_shared_strict(s: Bytes) -> Result<Uri, InvalidUri> {
use self::ErrorKind::*;

if s.len() > MAX_LEN {
return Err(TooLong.into());
}

match s.len() {
0 => {
return Err(Empty.into());
}
1 => match s[0] {
b'/' => {
return Ok(Uri {
scheme: Scheme::empty(),
authority: Authority::empty(),
path_and_query: PathAndQuery::slash(),
});
}
b'*' => {
return Ok(Uri {
scheme: Scheme::empty(),
authority: Authority::empty(),
path_and_query: PathAndQuery::star(),
});
}
_ => {
let authority = Authority::from_shared(s)?;

return Ok(Uri {
scheme: Scheme::empty(),
authority,
path_and_query: PathAndQuery::empty(),
});
}
},
_ => {}
}

if s[0] == b'/' {
return Ok(Uri {
scheme: Scheme::empty(),
authority: Authority::empty(),
path_and_query: PathAndQuery::from_shared_strict(s)?,
});
}

parse_full(s)
}

/// Convert a `Uri` from a static string.
///
/// This function will not perform any copying, however the string is
Expand All @@ -365,6 +416,15 @@ impl Uri {
}
}

/// Similar to [`from_static`] but marks `{` `}` and `"` as invalid.
pub fn from_static_strict(src: &'static str) -> Self {
let s = Bytes::from_static(src.as_bytes());
match Uri::from_shared_strict(s) {
Ok(uri) => uri,
Err(e) => panic!("static str is not valid URI: {}", e),
}
}

/// Convert a `Uri` into `Parts`.
///
/// # Note
Expand Down
81 changes: 81 additions & 0 deletions src/uri/path.rs
Expand Up @@ -105,6 +105,81 @@ impl PathAndQuery {
})
}

/// Similar to [`from_shared`] but does not accept `{`, `}` and `"`
pub(super) fn from_shared_strict(mut src: Bytes) -> Result<Self, InvalidUri> {
let mut query = NONE;
let mut fragment = None;

// block for iterator borrow
{
let mut iter = src.as_ref().iter().enumerate();

// path ...
for (i, &b) in &mut iter {
// See https://url.spec.whatwg.org/#path-state
match b {
b'?' => {
debug_assert_eq!(query, NONE);
query = i as u16;
break;
}
b'#' => {
fragment = Some(i);
break;
}

// This is the range of bytes that don't need to be
// percent-encoded in the path. If it should have been
// percent-encoded, then error.
#[rustfmt::skip]
0x21 |
0x24..=0x3B |
0x3D |
0x40..=0x5F |
0x61..=0x7A |
0x7C |
0x7E => {}

_ => return Err(ErrorKind::InvalidUriChar.into()),
}
}

// query ...
if query != NONE {
for (i, &b) in iter {
match b {
// While queries *should* be percent-encoded, most
// bytes are actually allowed...
// See https://url.spec.whatwg.org/#query-state
//
// Allowed: 0x21 / 0x24 - 0x3B / 0x3D / 0x3F - 0x7E
#[rustfmt::skip]
0x21 |
0x24..=0x3B |
0x3D |
0x3F..=0x7E => {}

b'#' => {
fragment = Some(i);
break;
}

_ => return Err(ErrorKind::InvalidUriChar.into()),
}
}
}
}

if let Some(i) = fragment {
src.truncate(i);
}

Ok(PathAndQuery {
data: unsafe { ByteStr::from_utf8_unchecked(src) },
query,
})
}

/// Convert a `PathAndQuery` from a static string.
///
/// This function will not perform any copying, however the string is
Expand Down Expand Up @@ -564,6 +639,12 @@ mod tests {
);
}

#[test]
fn fails_json_on_strict() {
let pq_bytes = r#"/{"bread":"baguette"}"#.as_bytes();
PathAndQuery::from_shared_strict(pq_bytes.into()).expect_err("should err");
}

fn pq(s: &str) -> PathAndQuery {
s.parse().expect(&format!("parsing {}", s))
}
Expand Down