Skip to content

Commit dccd373

Browse files
committed
Add Request::parse_url.
1 parent 78c5d35 commit dccd373

File tree

5 files changed

+70
-3
lines changed

5 files changed

+70
-3
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ keywords = ["web", "http", "server", "async", "threaded"]
77
license = "MIT OR Apache-2.0"
88
name = "servlin"
99
repository = "https://github.com/mleonhard/servlin"
10-
version = "0.7.0"
10+
version = "0.7.1"
1111

1212
[features]
1313
default = []

Readme.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Symbols:
103103
104104
Functions Expressions Impls Traits Methods Dependency
105105
106-
0/0 0/0 0/0 0/0 0/0 🔒 servlin 0.7.0
106+
0/0 0/0 0/0 0/0 0/0 🔒 servlin 0.7.1
107107
0/0 0/0 0/0 0/0 0/0 🔒 ├── safina 0.6.0
108108
0/0 0/0 0/0 0/0 0/0 🔒 │ └── safina-macros 0.1.3
109109
0/0 0/0 0/0 0/0 0/0 🔒 │ ├── safe-proc-macro2 1.0.67
@@ -234,6 +234,7 @@ Functions Expressions Impls Traits Methods Dependency
234234
See [rust-webserver-comparison.md](https://github.com/mleonhard/servlin/blob/main/rust-webserver-comparison.md).
235235

236236
# Changelog
237+
- v0.7.1 2024-11-16 - Add [`Request::parse_url`].
237238
- v0.7.0 2024-11-06
238239
- `log_request_and_response` to log `duration_ms` tag.
239240
- Fix typo in function name `Response::internal_server_errror_500`.

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
//! See [rust-webserver-comparison.md](https://github.com/mleonhard/servlin/blob/main/rust-webserver-comparison.md).
102102
//!
103103
//! # Changelog
104+
//! - v0.7.1 2024-11-16 - Add [`Request::parse_url`].
104105
//! - v0.7.0 2024-11-06
105106
//! - `log_request_and_response` to log `duration_ms` tag.
106107
//! - Fix typo in function name `Response::internal_server_errror_500`.

src/request.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ impl Request {
9595
panic!("error reading body: {e}");
9696
}
9797
serde_urlencoded::from_bytes(&buf).map_err(|e| {
98-
// Does this make a cross-site-scripting (XSS) vulnerability?
9998
Response::text(
10099
400,
101100
format!(
@@ -157,6 +156,29 @@ impl Request {
157156
Ok(Some(self.json()?))
158157
}
159158
}
159+
160+
/// Parses the request URL and deserializes it into type `T`.
161+
///
162+
/// Treats a missing URL query string (`/foo`) as an empty query string (`/foo?`).
163+
///
164+
/// # Errors
165+
/// Returns an error when
166+
/// - the URL parameters are mal-formed
167+
/// - we fail to deserialize the URL parameters into a `T`
168+
#[cfg(feature = "urlencoded")]
169+
pub fn parse_url<T: serde::de::DeserializeOwned>(&self) -> Result<T, Response> {
170+
use crate::util::escape_and_elide;
171+
let url_str = self.url.query().unwrap_or_default();
172+
serde_urlencoded::from_str(url_str).map_err(|e| {
173+
Response::text(
174+
400,
175+
format!(
176+
"error processing url: {}",
177+
escape_and_elide(e.to_string().as_bytes(), 100)
178+
),
179+
)
180+
})
181+
}
160182
}
161183
impl Debug for Request {
162184
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {

tests/urlencoded.rs

+43
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,49 @@ use crate::test_util::TestServer;
55
use serde::Deserialize;
66
use servlin::Response;
77

8+
#[test]
9+
fn parse_url() {
10+
let server = TestServer::start(|req| {
11+
#[derive(Deserialize)]
12+
struct Input {
13+
num: usize,
14+
msg: String,
15+
}
16+
let input: Input = match req.parse_url() {
17+
Ok(input) => input,
18+
Err(response) => return response,
19+
};
20+
assert_eq!(111, input.num);
21+
assert_eq!("aaa", input.msg.as_str());
22+
Response::redirect_303("/")
23+
})
24+
.unwrap();
25+
assert_eq!(
26+
server
27+
.exchange("M /?num=111&msg=aaa HTTP/1.1\r\n\r\n")
28+
.unwrap(),
29+
"HTTP/1.1 303 See Other\r\ncontent-length: 0\r\nlocation: /\r\n\r\n",
30+
);
31+
assert_eq!(
32+
server
33+
.exchange("M /?num=not_an_integer&msg=aaa HTTP/1.1\r\n\r\n")
34+
.unwrap(),
35+
"HTTP/1.1 400 Bad Request\r\ncontent-type: text/plain; charset=UTF-8\r\ncontent-length: 51\r\n\r\nerror processing url: invalid digit found in string",
36+
);
37+
assert_eq!(
38+
server
39+
.exchange("M /? HTTP/1.1\r\n\r\n")
40+
.unwrap(),
41+
"HTTP/1.1 400 Bad Request\r\ncontent-type: text/plain; charset=UTF-8\r\ncontent-length: 41\r\n\r\nerror processing url: missing field `num`",
42+
);
43+
assert_eq!(
44+
server
45+
.exchange("M / HTTP/1.1\r\n\r\n")
46+
.unwrap(),
47+
"HTTP/1.1 400 Bad Request\r\ncontent-type: text/plain; charset=UTF-8\r\ncontent-length: 41\r\n\r\nerror processing url: missing field `num`",
48+
);
49+
}
50+
851
#[test]
952
fn urlencoded() {
1053
let server = TestServer::start(|req| {

0 commit comments

Comments
 (0)