pingora_http/
authority.rs1#[derive(Debug, PartialEq, Eq)]
29pub enum RawTargetAuthority<'a> {
30 None,
32 Absolute {
34 scheme: &'a [u8],
36 authority: &'a [u8],
38 path_and_query: &'a [u8],
40 },
41 AmbiguousAuthority,
45}
46
47impl<'a> RawTargetAuthority<'a> {
48 pub fn authority(&self) -> Option<&'a [u8]> {
50 match self {
51 Self::None | Self::AmbiguousAuthority => None,
52 Self::Absolute { authority, .. } => Some(authority),
53 }
54 }
55}
56
57pub fn raw_target_authority(target: &[u8]) -> RawTargetAuthority<'_> {
59 if target.first() == Some(&b'/') {
61 return RawTargetAuthority::None;
62 }
63
64 let mut valid_scheme = true;
68 let mut scheme_end = None;
69 for (index, &byte) in target.iter().enumerate() {
70 if byte == b':' {
71 scheme_end = Some(index);
72 break;
73 }
74 valid_scheme &= if index == 0 {
75 byte.is_ascii_alphabetic()
76 } else {
77 byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.')
78 };
79 }
80 let Some(scheme_end) = scheme_end else {
81 return RawTargetAuthority::None;
82 };
83 let scheme = &target[..scheme_end];
84 let remainder = &target[scheme_end + 1..];
85
86 let authority = remainder.strip_prefix(b"//");
87
88 if scheme.is_empty() || !valid_scheme {
89 return if authority.is_some() {
92 RawTargetAuthority::AmbiguousAuthority
93 } else {
94 RawTargetAuthority::None
95 };
96 }
97
98 let http_family_scheme = is_http_family_scheme(scheme);
99 let Some(authority) = authority else {
100 return if http_family_scheme {
103 RawTargetAuthority::AmbiguousAuthority
104 } else {
105 RawTargetAuthority::None
106 };
107 };
108
109 let mut end = authority.len();
111 for (index, &byte) in authority.iter().enumerate() {
112 if matches!(byte, b'/' | b'?' | b'#') {
113 end = index;
114 break;
115 }
116 if http_family_scheme && byte == b'\\' {
118 return RawTargetAuthority::AmbiguousAuthority;
119 }
120 }
121 if http_family_scheme && end == 0 {
122 return RawTargetAuthority::AmbiguousAuthority;
123 }
124 RawTargetAuthority::Absolute {
125 scheme,
126 authority: &authority[..end],
127 path_and_query: &authority[end..],
128 }
129}
130
131pub fn is_http_family_scheme(scheme: &[u8]) -> bool {
138 [b"http".as_slice(), b"https", b"ws", b"wss"]
139 .iter()
140 .any(|candidate| scheme.eq_ignore_ascii_case(candidate))
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn classify_raw_target_authority() {
149 assert_eq!(
150 raw_target_authority(b"http://authority.example/test"),
151 RawTargetAuthority::Absolute {
152 scheme: b"http",
153 authority: b"authority.example",
154 path_and_query: b"/test"
155 }
156 );
157 assert_eq!(
158 raw_target_authority(b"http://authority.example/test").authority(),
159 Some(b"authority.example".as_slice())
160 );
161 assert_eq!(
162 raw_target_authority(b"http://user@authority.example:8443/test?a=b"),
163 RawTargetAuthority::Absolute {
164 scheme: b"http",
165 authority: b"user@authority.example:8443",
166 path_and_query: b"/test?a=b"
167 }
168 );
169 assert_eq!(
170 raw_target_authority(b"http://authority.example"),
171 RawTargetAuthority::Absolute {
172 scheme: b"http",
173 authority: b"authority.example",
174 path_and_query: b""
175 }
176 );
177 assert_eq!(
178 raw_target_authority(b"http:/\\/\\authority.example/test"),
179 RawTargetAuthority::AmbiguousAuthority
180 );
181 assert_eq!(
182 raw_target_authority(b"https:authority.example/test"),
183 RawTargetAuthority::AmbiguousAuthority
184 );
185 assert_eq!(
186 raw_target_authority(b"http://good.example\\evil.example/"),
187 RawTargetAuthority::AmbiguousAuthority
188 );
189 for target in [
190 b"ht_tp://other.example/admin".as_slice(),
191 b"9x://other.example/admin",
192 b"http:///path",
193 b"https://?query",
194 b"ws://good.example\\evil.example/",
195 b"wss://good.example\\evil.example/",
196 ] {
197 assert_eq!(
198 raw_target_authority(target),
199 RawTargetAuthority::AmbiguousAuthority,
200 "{}",
201 String::from_utf8_lossy(target)
202 );
203 }
204 assert_eq!(
205 raw_target_authority(b"file:///path"),
206 RawTargetAuthority::Absolute {
207 scheme: b"file",
208 authority: b"",
209 path_and_query: b"/path"
210 }
211 );
212 assert_eq!(
213 raw_target_authority(b"ftp://good.example\\evil.example/"),
214 RawTargetAuthority::Absolute {
215 scheme: b"ftp",
216 authority: b"good.example\\evil.example",
217 path_and_query: b"/"
218 }
219 );
220 assert_eq!(raw_target_authority(b"/test"), RawTargetAuthority::None);
221 assert_eq!(raw_target_authority(b":opaque"), RawTargetAuthority::None);
222 assert_eq!(
223 raw_target_authority(b"/redirect?next=http://user@evil.example/"),
224 RawTargetAuthority::None
225 );
226 assert_eq!(
227 raw_target_authority(b"/test#http://user@evil.example/"),
228 RawTargetAuthority::None
229 );
230 assert_eq!(
231 raw_target_authority(b"foo:bar://user@example/path"),
232 RawTargetAuthority::None
233 );
234 }
235}