Skip to content

Commit 4814999

Browse files
authored
Merge pull request #9473 from LawnGnome/null-bytes
search: check for null bytes before querying
2 parents c96dfb3 + 110e569 commit 4814999

7 files changed

+85
-23
lines changed

src/controllers/krate/search.rs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,28 +55,29 @@ pub async fn search(app: AppState, req: Parts) -> AppResult<Json<Value>> {
5555
use seek::*;
5656

5757
let params = req.query();
58-
let option_param = |s| params.get(s).map(|v| v.as_str());
59-
let sort = option_param("sort");
60-
let include_yanked = option_param("include_yanked")
58+
let option_param = |s| match params.get(s).map(|v| v.as_str()) {
59+
Some(v) if v.contains('\0') => Err(bad_request(format!(
60+
"parameter {s} cannot contain a null byte"
61+
))),
62+
Some(v) => Ok(Some(v)),
63+
None => Ok(None),
64+
};
65+
let sort = option_param("sort")?;
66+
let include_yanked = option_param("include_yanked")?
6167
.map(|s| s == "yes")
6268
.unwrap_or(true);
6369

64-
// Remove 0x00 characters from the query string because Postgres can not
65-
// handle them and will return an error, which would cause us to throw
66-
// an Internal Server Error ourselves.
67-
let q_string = option_param("q").map(|q| q.replace('\u{0}', ""));
68-
6970
let filter_params = FilterParams {
70-
q_string: q_string.as_deref(),
71+
q_string: option_param("q")?,
7172
include_yanked,
72-
category: option_param("category"),
73-
all_keywords: option_param("all_keywords"),
74-
keyword: option_param("keyword"),
75-
letter: option_param("letter"),
76-
user_id: option_param("user_id").and_then(|s| s.parse::<i32>().ok()),
77-
team_id: option_param("team_id").and_then(|s| s.parse::<i32>().ok()),
78-
following: option_param("following").is_some(),
79-
has_ids: option_param("ids[]").is_some(),
73+
category: option_param("category")?,
74+
all_keywords: option_param("all_keywords")?,
75+
keyword: option_param("keyword")?,
76+
letter: option_param("letter")?,
77+
user_id: option_param("user_id")?.and_then(|s| s.parse::<i32>().ok()),
78+
team_id: option_param("team_id")?.and_then(|s| s.parse::<i32>().ok()),
79+
following: option_param("following")?.is_some(),
80+
has_ids: option_param("ids[]")?.is_some(),
8081
..Default::default()
8182
};
8283

@@ -95,7 +96,7 @@ pub async fn search(app: AppState, req: Parts) -> AppResult<Json<Value>> {
9596
.left_join(recent_crate_downloads::table)
9697
.select(selection);
9798

98-
if let Some(q_string) = &q_string {
99+
if let Some(q_string) = &filter_params.q_string {
99100
if !q_string.is_empty() {
100101
let sort = sort.unwrap_or("relevance");
101102

src/tests/routes/crates/list.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,6 @@ async fn index_queries() {
193193
assert_eq!(cl.crates.len(), 0);
194194
assert_eq!(cl.meta.total, 0);
195195
}
196-
197-
// ignores 0x00 characters that Postgres does not support
198-
for cl in search_both(&anon, "q=k%00w1").await {
199-
assert_eq!(cl.meta.total, 3);
200-
}
201196
}
202197

203198
#[tokio::test(flavor = "multi_thread")]
@@ -1015,6 +1010,17 @@ async fn test_pages_work_even_with_seek_based_pagination() {
10151010
assert_eq!(second.meta.total, 3);
10161011
}
10171012

1013+
#[tokio::test(flavor = "multi_thread")]
1014+
async fn invalid_params_with_null_bytes() {
1015+
let (_app, anon, _cookie) = TestApp::init().with_user();
1016+
1017+
for name in ["q", "category", "all_keywords", "keyword", "letter"] {
1018+
let response = anon.get::<()>(&format!("/api/v1/crates?{name}=%00")).await;
1019+
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
1020+
assert_json_snapshot!(response.json());
1021+
}
1022+
}
1023+
10181024
#[tokio::test(flavor = "multi_thread")]
10191025
async fn invalid_seek_parameter() {
10201026
let (_app, anon, _cookie) = TestApp::init().with_user();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
source: src/tests/routes/crates/list.rs
3+
expression: response.json()
4+
---
5+
{
6+
"errors": [
7+
{
8+
"detail": "parameter category cannot contain a null byte"
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
source: src/tests/routes/crates/list.rs
3+
expression: response.json()
4+
---
5+
{
6+
"errors": [
7+
{
8+
"detail": "parameter all_keywords cannot contain a null byte"
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
source: src/tests/routes/crates/list.rs
3+
expression: response.json()
4+
---
5+
{
6+
"errors": [
7+
{
8+
"detail": "parameter keyword cannot contain a null byte"
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
source: src/tests/routes/crates/list.rs
3+
expression: response.json()
4+
---
5+
{
6+
"errors": [
7+
{
8+
"detail": "parameter letter cannot contain a null byte"
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
source: src/tests/routes/crates/list.rs
3+
expression: response.json()
4+
---
5+
{
6+
"errors": [
7+
{
8+
"detail": "parameter q cannot contain a null byte"
9+
}
10+
]
11+
}

0 commit comments

Comments
 (0)