summaryrefslogtreecommitdiff
path: root/vendor/http/src/uri/path.rs
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-15 16:37:08 -0600
committermo khan <mo@mokhan.ca>2025-07-17 16:30:22 -0600
commit45df4d0d9b577fecee798d672695fe24ff57fb1b (patch)
tree1b99bf645035b58e0d6db08c7a83521f41f7a75b /vendor/http/src/uri/path.rs
parentf94f79608393d4ab127db63cc41668445ef6b243 (diff)
feat: migrate from Cedar to SpiceDB authorization system
This is a major architectural change that replaces the Cedar policy-based authorization system with SpiceDB's relation-based authorization. Key changes: - Migrate from Rust to Go implementation - Replace Cedar policies with SpiceDB schema and relationships - Switch from envoy `ext_authz` with Cedar to SpiceDB permission checks - Update build system and dependencies for Go ecosystem - Maintain Envoy integration for external authorization This change enables more flexible permission modeling through SpiceDB's Google Zanzibar inspired relation-based system, supporting complex hierarchical permissions that were difficult to express in Cedar. Breaking change: Existing Cedar policies and Rust-based configuration will no longer work and need to be migrated to SpiceDB schema.
Diffstat (limited to 'vendor/http/src/uri/path.rs')
-rw-r--r--vendor/http/src/uri/path.rs604
1 files changed, 0 insertions, 604 deletions
diff --git a/vendor/http/src/uri/path.rs b/vendor/http/src/uri/path.rs
deleted file mode 100644
index 42db1f92..00000000
--- a/vendor/http/src/uri/path.rs
+++ /dev/null
@@ -1,604 +0,0 @@
-use std::convert::TryFrom;
-use std::str::FromStr;
-use std::{cmp, fmt, hash, str};
-
-use bytes::Bytes;
-
-use super::{ErrorKind, InvalidUri};
-use crate::byte_str::ByteStr;
-
-/// Represents the path component of a URI
-#[derive(Clone)]
-pub struct PathAndQuery {
- pub(super) data: ByteStr,
- pub(super) query: u16,
-}
-
-const NONE: u16 = u16::MAX;
-
-impl PathAndQuery {
- // Not public while `bytes` is unstable.
- pub(super) fn from_shared(mut src: Bytes) -> Result<Self, InvalidUri> {
- let mut query = NONE;
- let mut fragment = None;
-
- let mut is_maybe_not_utf8 = false;
-
- // 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 => {}
-
- // potentially utf8, might not, should check
- 0x7F..=0xFF => {
- is_maybe_not_utf8 = true;
- }
-
- // These are code points that are supposed to be
- // percent-encoded in the path but there are clients
- // out there sending them as is and httparse accepts
- // to parse those requests, so they are allowed here
- // for parity.
- //
- // For reference, those are code points that are used
- // to send requests with JSON directly embedded in
- // the URI path. Yes, those things happen for real.
- #[rustfmt::skip]
- b'"' |
- b'{' | b'}' => {}
-
- _ => 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 => {}
-
- 0x7F..=0xFF => {
- is_maybe_not_utf8 = true;
- }
-
- b'#' => {
- fragment = Some(i);
- break;
- }
-
- _ => return Err(ErrorKind::InvalidUriChar.into()),
- }
- }
- }
- }
-
- if let Some(i) = fragment {
- src.truncate(i);
- }
-
- let data = if is_maybe_not_utf8 {
- ByteStr::from_utf8(src).map_err(|_| ErrorKind::InvalidUriChar)?
- } else {
- unsafe { ByteStr::from_utf8_unchecked(src) }
- };
-
- Ok(PathAndQuery { data, query })
- }
-
- /// Convert a `PathAndQuery` from a static string.
- ///
- /// This function will not perform any copying, however the string is
- /// checked to ensure that it is valid.
- ///
- /// # Panics
- ///
- /// This function panics if the argument is an invalid path and query.
- ///
- /// # Examples
- ///
- /// ```
- /// # use http::uri::*;
- /// let v = PathAndQuery::from_static("/hello?world");
- ///
- /// assert_eq!(v.path(), "/hello");
- /// assert_eq!(v.query(), Some("world"));
- /// ```
- #[inline]
- pub fn from_static(src: &'static str) -> Self {
- let src = Bytes::from_static(src.as_bytes());
-
- PathAndQuery::from_shared(src).unwrap()
- }
-
- /// Attempt to convert a `Bytes` buffer to a `PathAndQuery`.
- ///
- /// This will try to prevent a copy if the type passed is the type used
- /// internally, and will copy the data if it is not.
- pub fn from_maybe_shared<T>(src: T) -> Result<Self, InvalidUri>
- where
- T: AsRef<[u8]> + 'static,
- {
- if_downcast_into!(T, Bytes, src, {
- return PathAndQuery::from_shared(src);
- });
-
- PathAndQuery::try_from(src.as_ref())
- }
-
- pub(super) fn empty() -> Self {
- PathAndQuery {
- data: ByteStr::new(),
- query: NONE,
- }
- }
-
- pub(super) fn slash() -> Self {
- PathAndQuery {
- data: ByteStr::from_static("/"),
- query: NONE,
- }
- }
-
- pub(super) fn star() -> Self {
- PathAndQuery {
- data: ByteStr::from_static("*"),
- query: NONE,
- }
- }
-
- /// Returns the path component
- ///
- /// The path component is **case sensitive**.
- ///
- /// ```notrust
- /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
- /// |--------|
- /// |
- /// path
- /// ```
- ///
- /// If the URI is `*` then the path component is equal to `*`.
- ///
- /// # Examples
- ///
- /// ```
- /// # use http::uri::*;
- ///
- /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap();
- ///
- /// assert_eq!(path_and_query.path(), "/hello/world");
- /// ```
- #[inline]
- pub fn path(&self) -> &str {
- let ret = if self.query == NONE {
- &self.data[..]
- } else {
- &self.data[..self.query as usize]
- };
-
- if ret.is_empty() {
- return "/";
- }
-
- ret
- }
-
- /// Returns the query string component
- ///
- /// The query component contains non-hierarchical data that, along with data
- /// in the path component, serves to identify a resource within the scope of
- /// the URI's scheme and naming authority (if any). The query component is
- /// indicated by the first question mark ("?") character and terminated by a
- /// number sign ("#") character or by the end of the URI.
- ///
- /// ```notrust
- /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
- /// |-------------------|
- /// |
- /// query
- /// ```
- ///
- /// # Examples
- ///
- /// With a query string component
- ///
- /// ```
- /// # use http::uri::*;
- /// let path_and_query: PathAndQuery = "/hello/world?key=value&foo=bar".parse().unwrap();
- ///
- /// assert_eq!(path_and_query.query(), Some("key=value&foo=bar"));
- /// ```
- ///
- /// Without a query string component
- ///
- /// ```
- /// # use http::uri::*;
- /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap();
- ///
- /// assert!(path_and_query.query().is_none());
- /// ```
- #[inline]
- pub fn query(&self) -> Option<&str> {
- if self.query == NONE {
- None
- } else {
- let i = self.query + 1;
- Some(&self.data[i as usize..])
- }
- }
-
- /// Returns the path and query as a string component.
- ///
- /// # Examples
- ///
- /// With a query string component
- ///
- /// ```
- /// # use http::uri::*;
- /// let path_and_query: PathAndQuery = "/hello/world?key=value&foo=bar".parse().unwrap();
- ///
- /// assert_eq!(path_and_query.as_str(), "/hello/world?key=value&foo=bar");
- /// ```
- ///
- /// Without a query string component
- ///
- /// ```
- /// # use http::uri::*;
- /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap();
- ///
- /// assert_eq!(path_and_query.as_str(), "/hello/world");
- /// ```
- #[inline]
- pub fn as_str(&self) -> &str {
- let ret = &self.data[..];
- if ret.is_empty() {
- return "/";
- }
- ret
- }
-}
-
-impl<'a> TryFrom<&'a [u8]> for PathAndQuery {
- type Error = InvalidUri;
- #[inline]
- fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
- PathAndQuery::from_shared(Bytes::copy_from_slice(s))
- }
-}
-
-impl<'a> TryFrom<&'a str> for PathAndQuery {
- type Error = InvalidUri;
- #[inline]
- fn try_from(s: &'a str) -> Result<Self, Self::Error> {
- TryFrom::try_from(s.as_bytes())
- }
-}
-
-impl TryFrom<Vec<u8>> for PathAndQuery {
- type Error = InvalidUri;
- #[inline]
- fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
- PathAndQuery::from_shared(vec.into())
- }
-}
-
-impl TryFrom<String> for PathAndQuery {
- type Error = InvalidUri;
- #[inline]
- fn try_from(s: String) -> Result<Self, Self::Error> {
- PathAndQuery::from_shared(s.into())
- }
-}
-
-impl TryFrom<&String> for PathAndQuery {
- type Error = InvalidUri;
- #[inline]
- fn try_from(s: &String) -> Result<Self, Self::Error> {
- TryFrom::try_from(s.as_bytes())
- }
-}
-
-impl FromStr for PathAndQuery {
- type Err = InvalidUri;
- #[inline]
- fn from_str(s: &str) -> Result<Self, InvalidUri> {
- TryFrom::try_from(s)
- }
-}
-
-impl fmt::Debug for PathAndQuery {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- fmt::Display::fmt(self, f)
- }
-}
-
-impl fmt::Display for PathAndQuery {
- fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
- if !self.data.is_empty() {
- match self.data.as_bytes()[0] {
- b'/' | b'*' => write!(fmt, "{}", &self.data[..]),
- _ => write!(fmt, "/{}", &self.data[..]),
- }
- } else {
- write!(fmt, "/")
- }
- }
-}
-
-impl hash::Hash for PathAndQuery {
- fn hash<H: hash::Hasher>(&self, state: &mut H) {
- self.data.hash(state);
- }
-}
-
-// ===== PartialEq / PartialOrd =====
-
-impl PartialEq for PathAndQuery {
- #[inline]
- fn eq(&self, other: &PathAndQuery) -> bool {
- self.data == other.data
- }
-}
-
-impl Eq for PathAndQuery {}
-
-impl PartialEq<str> for PathAndQuery {
- #[inline]
- fn eq(&self, other: &str) -> bool {
- self.as_str() == other
- }
-}
-
-impl<'a> PartialEq<PathAndQuery> for &'a str {
- #[inline]
- fn eq(&self, other: &PathAndQuery) -> bool {
- self == &other.as_str()
- }
-}
-
-impl<'a> PartialEq<&'a str> for PathAndQuery {
- #[inline]
- fn eq(&self, other: &&'a str) -> bool {
- self.as_str() == *other
- }
-}
-
-impl PartialEq<PathAndQuery> for str {
- #[inline]
- fn eq(&self, other: &PathAndQuery) -> bool {
- self == other.as_str()
- }
-}
-
-impl PartialEq<String> for PathAndQuery {
- #[inline]
- fn eq(&self, other: &String) -> bool {
- self.as_str() == other.as_str()
- }
-}
-
-impl PartialEq<PathAndQuery> for String {
- #[inline]
- fn eq(&self, other: &PathAndQuery) -> bool {
- self.as_str() == other.as_str()
- }
-}
-
-impl PartialOrd for PathAndQuery {
- #[inline]
- fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
- self.as_str().partial_cmp(other.as_str())
- }
-}
-
-impl PartialOrd<str> for PathAndQuery {
- #[inline]
- fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
- self.as_str().partial_cmp(other)
- }
-}
-
-impl PartialOrd<PathAndQuery> for str {
- #[inline]
- fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
- self.partial_cmp(other.as_str())
- }
-}
-
-impl<'a> PartialOrd<&'a str> for PathAndQuery {
- #[inline]
- fn partial_cmp(&self, other: &&'a str) -> Option<cmp::Ordering> {
- self.as_str().partial_cmp(*other)
- }
-}
-
-impl<'a> PartialOrd<PathAndQuery> for &'a str {
- #[inline]
- fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
- self.partial_cmp(&other.as_str())
- }
-}
-
-impl PartialOrd<String> for PathAndQuery {
- #[inline]
- fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
- self.as_str().partial_cmp(other.as_str())
- }
-}
-
-impl PartialOrd<PathAndQuery> for String {
- #[inline]
- fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
- self.as_str().partial_cmp(other.as_str())
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn equal_to_self_of_same_path() {
- let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
- let p2: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
- assert_eq!(p1, p2);
- assert_eq!(p2, p1);
- }
-
- #[test]
- fn not_equal_to_self_of_different_path() {
- let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
- let p2: PathAndQuery = "/world&foo=bar".parse().unwrap();
- assert_ne!(p1, p2);
- assert_ne!(p2, p1);
- }
-
- #[test]
- fn equates_with_a_str() {
- let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
- assert_eq!(&path_and_query, "/hello/world&foo=bar");
- assert_eq!("/hello/world&foo=bar", &path_and_query);
- assert_eq!(path_and_query, "/hello/world&foo=bar");
- assert_eq!("/hello/world&foo=bar", path_and_query);
- }
-
- #[test]
- fn not_equal_with_a_str_of_a_different_path() {
- let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
- // as a reference
- assert_ne!(&path_and_query, "/hello&foo=bar");
- assert_ne!("/hello&foo=bar", &path_and_query);
- // without reference
- assert_ne!(path_and_query, "/hello&foo=bar");
- assert_ne!("/hello&foo=bar", path_and_query);
- }
-
- #[test]
- fn equates_with_a_string() {
- let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
- assert_eq!(path_and_query, "/hello/world&foo=bar".to_string());
- assert_eq!("/hello/world&foo=bar".to_string(), path_and_query);
- }
-
- #[test]
- fn not_equal_with_a_string_of_a_different_path() {
- let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
- assert_ne!(path_and_query, "/hello&foo=bar".to_string());
- assert_ne!("/hello&foo=bar".to_string(), path_and_query);
- }
-
- #[test]
- fn compares_to_self() {
- let p1: PathAndQuery = "/a/world&foo=bar".parse().unwrap();
- let p2: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
- assert!(p1 < p2);
- assert!(p2 > p1);
- }
-
- #[test]
- fn compares_with_a_str() {
- let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
- // by ref
- assert!(&path_and_query < "/c/world&foo=bar");
- assert!("/c/world&foo=bar" > &path_and_query);
- assert!(&path_and_query > "/a/world&foo=bar");
- assert!("/a/world&foo=bar" < &path_and_query);
-
- // by val
- assert!(path_and_query < "/c/world&foo=bar");
- assert!("/c/world&foo=bar" > path_and_query);
- assert!(path_and_query > "/a/world&foo=bar");
- assert!("/a/world&foo=bar" < path_and_query);
- }
-
- #[test]
- fn compares_with_a_string() {
- let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
- assert!(path_and_query < "/c/world&foo=bar".to_string());
- assert!("/c/world&foo=bar".to_string() > path_and_query);
- assert!(path_and_query > "/a/world&foo=bar".to_string());
- assert!("/a/world&foo=bar".to_string() < path_and_query);
- }
-
- #[test]
- fn ignores_valid_percent_encodings() {
- assert_eq!("/a%20b", pq("/a%20b?r=1").path());
- assert_eq!("qr=%31", pq("/a/b?qr=%31").query().unwrap());
- }
-
- #[test]
- fn ignores_invalid_percent_encodings() {
- assert_eq!("/a%%b", pq("/a%%b?r=1").path());
- assert_eq!("/aaa%", pq("/aaa%").path());
- assert_eq!("/aaa%", pq("/aaa%?r=1").path());
- assert_eq!("/aa%2", pq("/aa%2").path());
- assert_eq!("/aa%2", pq("/aa%2?r=1").path());
- assert_eq!("qr=%3", pq("/a/b?qr=%3").query().unwrap());
- }
-
- #[test]
- fn allow_utf8_in_path() {
- assert_eq!("/🍕", pq("/🍕").path());
- }
-
- #[test]
- fn allow_utf8_in_query() {
- assert_eq!(Some("pizza=🍕"), pq("/test?pizza=🍕").query());
- }
-
- #[test]
- fn rejects_invalid_utf8_in_path() {
- PathAndQuery::try_from(&[b'/', 0xFF][..]).expect_err("reject invalid utf8");
- }
-
- #[test]
- fn rejects_invalid_utf8_in_query() {
- PathAndQuery::try_from(&[b'/', b'a', b'?', 0xFF][..]).expect_err("reject invalid utf8");
- }
-
- #[test]
- fn json_is_fine() {
- assert_eq!(
- r#"/{"bread":"baguette"}"#,
- pq(r#"/{"bread":"baguette"}"#).path()
- );
- }
-
- fn pq(s: &str) -> PathAndQuery {
- s.parse().expect(&format!("parsing {}", s))
- }
-}