hyper_simple_server/
sanitize.rs

1
2
3use crate::prelude::*;
4
5
6
7
8#[ cfg (feature = "hss-sanitize") ]
9pub fn sanitize_scheme (_scheme : &Scheme) -> ServerResult<Option<Scheme>> {
10	
11	// NOTE:  The `_uri.scheme` is always safe, according to the `http::uri::scheme::Scheme2::parse_exact` implementation.
12	
13	Ok (None)
14}
15
16
17
18
19#[ cfg (feature = "hss-sanitize") ]
20pub fn sanitize_authority (_authority : &Authority) -> ServerResult<Option<Authority>> {
21	
22	let _authority = _authority.as_str ();
23	
24	if let Some (_offset) = _authority.find ('@') {
25		
26		// NOTE:  The rest of `_uri.authority` is always safe, according to the `http::uri::authority::Authority::parse` implementation.
27		let _authority = Authority::try_from (&_authority[_offset + 1 ..]) .or_wrap (0xda6f459d) ?;
28		
29		Ok (Some (_authority))
30		
31	} else {
32		Ok (None)
33	}
34}
35
36
37
38
39#[ cfg (feature = "hss-sanitize") ]
40pub fn sanitize_path (_path : &str) -> ServerResult<Option<String>> {
41	
42	let _path_as_str = _path;
43	let _path_as_bytes = _path_as_str.as_bytes ();
44	drop (_path);
45	
46	match _path_as_bytes {
47		
48		b"" =>
49			// NOTE:  If the buffer is empty, it will always return `/` (and we can't detect this case), thus this branch is just a safety-net.
50			Ok (Some (String::from ("/"))),
51		b"/" | b"*" =>
52			Ok (None),
53		
54		_ if _path_as_bytes[0] != b'/' =>
55			return Err (error_with_code (0x0705e550)),
56		
57		_ => {
58			
59			let mut _normalize = false;
60			if !_normalize && _path_as_str.contains ("//") {
61				_normalize = true;
62			}
63			if !_normalize && (_path_as_str.contains ("/./") || _path_as_bytes.ends_with (b"/.")) {
64				_normalize = true;
65			}
66			if !_normalize && (_path_as_str.contains ("/../") || _path_as_bytes.ends_with (b"/..")) {
67				_normalize = true;
68			}
69			if !_normalize && _path_as_bytes.contains (&b'%') {
70				_normalize = true;
71			}
72			
73			if _normalize {
74				
75				let mut _buffer = Vec::with_capacity (_path_as_bytes.len ());
76				enum State {
77					Normal,
78					Percent0,
79					Percent1 (u8),
80				}
81				let mut _rest = _path_as_bytes;
82				let mut _last_push = None;
83				let mut _last_state = State::Normal;
84				
85				loop {
86					
87					let _current = if let [_head, _tail @ ..] = _rest {
88						_rest = _tail;
89						*_head
90					} else {
91						break;
92					};
93					
94					let (mut _next_push, _next_state) = match _last_state {
95						State::Normal =>
96							// NOTE:  Based on `http::uri::PathAndQuery::from_shared` implementation.
97							match _current {
98								// NOTE:  These two cases should always be percent-encoded, thus this branch is just a safety-net.
99								b'?' | b'#' =>
100									(Some (_current), State::Normal),
101								b'%' =>
102									(None, State::Percent0),
103								0x21 | 0x24 ..= 0x3B | 0x3D | 0x40 ..= 0x5F | 0x61 ..= 0x7A | 0x7C | 0x7E =>
104									(Some (_current), State::Normal),
105								_ =>
106									return Err (error_with_code (0x9c6c8644)),
107							},
108						State::Percent0 | State::Percent1 (_) => {
109							let _digit_2 = match _current {
110								b'0' ..= b'9' => _current - b'0',
111								b'A' ..= b'F' => _current - b'A' + 10,
112								b'a' ..= b'f' => _current - b'a' + 10,
113								_ =>
114									return Err (error_with_code (0x563f825c)),
115							};
116							match _last_state {
117								State::Percent0 =>
118									(None, State::Percent1 (_digit_2)),
119								State::Percent1 (_digit_1) => {
120									let _byte = (_digit_1 << 4) | _digit_2;
121									(Some (_byte), State::Normal)
122								}
123								_ =>
124									panic_with_code (0xacb15742),
125							}
126						}
127					};
128					
129					if (_next_push == Some (b'/')) && (_last_push == Some (b'/')) {
130						_next_push = None;
131					}
132					
133					if let Some (_next_push) = _next_push {
134						let _percent = match _next_push {
135							b'?' | b'#' | b'%' =>
136								true,
137							0x21 | 0x24 ..= 0x3B | 0x3D | 0x40 ..= 0x5F | 0x61 ..= 0x7A | 0x7C | 0x7E =>
138								false,
139							_ =>
140								true,
141						};
142						if _percent {
143							let _digit_1 = _next_push >> 4;
144							let _digit_2 = _next_push & 0x0f;
145							_buffer.push (b'%');
146							_buffer.push (if _digit_1 <= 9 { _digit_1 + b'0' } else { _digit_1 - 10 + b'A' });
147							_buffer.push (if _digit_2 <= 9 { _digit_2 + b'0' } else { _digit_2 - 10 + b'A' });
148						} else {
149							_buffer.push (_next_push);
150						}
151					}
152					
153					_last_push = _next_push;
154					_last_state = _next_state;
155				}
156				
157				match _last_state {
158					State::Normal => (),
159					State::Percent0 | State::Percent1 (_) =>
160						return Err (error_with_code (0x574c1224)),
161				}
162				
163				let mut _buffer = String::from_utf8 (_buffer) .or_wrap (0x88642ea3) ?;
164				
165				while let Some (_offset) = _buffer.rfind ("//") {
166					_buffer.remove (_offset);
167				}
168				if _buffer.ends_with ("/.") {
169					_buffer.push ('/');
170				}
171				while let Some (_offset) = _buffer.rfind ("/./") {
172					_buffer.replace_range (_offset .. _offset + 3, "/");
173				}
174				if _buffer.ends_with ("/..") {
175					_buffer.push ('/');
176				}
177				while let Some (_offset_2) = _buffer.find ("/../") {
178					let _offset_1 = if _offset_2 > 0 { _buffer[0.._offset_2].rfind ('/') .or_panic (0x3693e145) } else { 0 };
179					_buffer.replace_range (_offset_1 .. _offset_2 + 4, "/");
180				}
181				
182				Ok (Some (_buffer))
183				
184			} else {
185				Ok (None)
186			}
187		}
188	}
189}
190
191
192
193
194#[ cfg (feature = "hss-sanitize") ]
195pub fn sanitize_query (_query : &str) -> ServerResult<Option<String>> {
196	
197	match _query {
198		
199		"" =>
200			Ok (Some (String::new ())),
201		
202		_ =>
203			// FIXME:  For the moment we don't validate queries!
204			Ok (None),
205	}
206}
207
208
209
210
211#[ cfg (feature = "hss-sanitize") ]
212pub fn sanitize_path_and_query (_path_and_query : &PathAndQuery) -> ServerResult<Option<PathAndQuery>> {
213	
214	let _path = sanitize_path (_path_and_query.path ()) ?;
215	
216	let _query = if let Some (_query) = _path_and_query.query () {
217		sanitize_query (_query) ?
218	} else {
219		None
220	};
221	
222	if _path.is_none () && _query.is_none () {
223		return Ok (None);
224	}
225	
226	let _path = _path.as_ref () .map (String::as_str) .unwrap_or_else (|| _path_and_query.path ());
227	let _query = _query.as_ref () .map (String::as_str) .or_else (|| _path_and_query.query ()) .unwrap_or ("");
228	
229	let mut _buffer = String::with_capacity (_path.len () + _query.len () + 1);
230	_buffer.push_str (_path);
231	if ! _query.is_empty () {
232		_buffer.push ('?');
233		_buffer.push_str (_query);
234	}
235	
236	let _path_and_query = PathAndQuery::try_from (_buffer) .or_wrap (0x7d1433ad) ?;
237	
238	Ok (Some (_path_and_query))
239}
240
241
242
243
244#[ cfg (feature = "hss-sanitize") ]
245pub fn sanitize_uri (_uri : &Uri) -> ServerResult<Option<Uri>> {
246	
247//	eprintln! ("[dd] [34b470a9]  `{}` | scheme: {:?} | authority: {:?} | path: {:?} | query: {:?}", _uri, _uri.scheme (), _uri.authority (), _uri.path (), _uri.query ());
248	
249	let _scheme = if let Some (_scheme) = _uri.scheme () {
250		sanitize_scheme (_scheme) ?
251	} else {
252		None
253	};
254	
255	let _authority = if let Some (_authority) = _uri.authority () {
256		sanitize_authority (_authority) ?
257	} else {
258		None
259	};
260	
261	let _path_and_query = if let Some (_path_and_query) = _uri.path_and_query () {
262		sanitize_path_and_query (_path_and_query) ?
263	} else {
264		// NOTE:  This seems to be a corner-case:  if one submits just `host`, one gets an authority, but not a path!
265		Some (PathAndQuery::from_static ("/"))
266	};
267	
268	if _scheme.is_none () && _authority.is_none () && _path_and_query.is_none () {
269		return Ok (None);
270	}
271	
272	// NOTE:  `http::uri::Uri::from_parts` fails if there is `authority` but not `scheme`, although this is what we get if one submits just `host`!
273	
274	let _uri_parts = _uri.clone ();
275	
276	#[ allow (unsafe_code) ]
277	let mut _uri_parts : (Scheme, Authority, PathAndQuery) = unsafe { mem::transmute (_uri_parts) };
278	
279	if let Some (_scheme) = _scheme {
280		_uri_parts.0 = _scheme;
281	}
282	if let Some (_authority) = _authority {
283		_uri_parts.1 = _authority;
284	}
285	if let Some (_path_and_query) = _path_and_query {
286		_uri_parts.2 = _path_and_query;
287	}
288	
289	#[ allow (unsafe_code) ]
290	let _uri : Uri = unsafe { mem::transmute (_uri_parts) };
291	
292	return Ok (Some (_uri));
293}
294