diff --git a/src/controllers/krate/search.rs b/src/controllers/krate/search.rs index f7d80bdb4b6..24c3b2ebfc3 100644 --- a/src/controllers/krate/search.rs +++ b/src/controllers/krate/search.rs @@ -55,28 +55,29 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { use seek::*; let params = req.query(); - let option_param = |s| params.get(s).map(|v| v.as_str()); - let sort = option_param("sort"); - let include_yanked = option_param("include_yanked") + let option_param = |s| match params.get(s).map(|v| v.as_str()) { + Some(v) if v.contains('\0') => Err(bad_request(format!( + "parameter {s} cannot contain a null byte" + ))), + Some(v) => Ok(Some(v)), + None => Ok(None), + }; + let sort = option_param("sort")?; + let include_yanked = option_param("include_yanked")? .map(|s| s == "yes") .unwrap_or(true); - // Remove 0x00 characters from the query string because Postgres can not - // handle them and will return an error, which would cause us to throw - // an Internal Server Error ourselves. - let q_string = option_param("q").map(|q| q.replace('\u{0}', "")); - let filter_params = FilterParams { - q_string: q_string.as_deref(), + q_string: option_param("q")?, include_yanked, - category: option_param("category"), - all_keywords: option_param("all_keywords"), - keyword: option_param("keyword"), - letter: option_param("letter"), - user_id: option_param("user_id").and_then(|s| s.parse::().ok()), - team_id: option_param("team_id").and_then(|s| s.parse::().ok()), - following: option_param("following").is_some(), - has_ids: option_param("ids[]").is_some(), + category: option_param("category")?, + all_keywords: option_param("all_keywords")?, + keyword: option_param("keyword")?, + letter: option_param("letter")?, + user_id: option_param("user_id")?.and_then(|s| s.parse::().ok()), + team_id: option_param("team_id")?.and_then(|s| s.parse::().ok()), + following: option_param("following")?.is_some(), + has_ids: option_param("ids[]")?.is_some(), ..Default::default() }; @@ -95,7 +96,7 @@ pub async fn search(app: AppState, req: Parts) -> AppResult> { .left_join(recent_crate_downloads::table) .select(selection); - if let Some(q_string) = &q_string { + if let Some(q_string) = &filter_params.q_string { if !q_string.is_empty() { let sort = sort.unwrap_or("relevance"); diff --git a/src/tests/routes/crates/list.rs b/src/tests/routes/crates/list.rs index f7d8b249448..05744375ad0 100644 --- a/src/tests/routes/crates/list.rs +++ b/src/tests/routes/crates/list.rs @@ -193,11 +193,6 @@ async fn index_queries() { assert_eq!(cl.crates.len(), 0); assert_eq!(cl.meta.total, 0); } - - // ignores 0x00 characters that Postgres does not support - for cl in search_both(&anon, "q=k%00w1").await { - assert_eq!(cl.meta.total, 3); - } } #[tokio::test(flavor = "multi_thread")] @@ -1015,6 +1010,17 @@ async fn test_pages_work_even_with_seek_based_pagination() { assert_eq!(second.meta.total, 3); } +#[tokio::test(flavor = "multi_thread")] +async fn invalid_params_with_null_bytes() { + let (_app, anon, _cookie) = TestApp::init().with_user(); + + for name in ["q", "category", "all_keywords", "keyword", "letter"] { + let response = anon.get::<()>(&format!("/api/v1/crates?{name}=%00")).await; + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + assert_json_snapshot!(response.json()); + } +} + #[tokio::test(flavor = "multi_thread")] async fn invalid_seek_parameter() { let (_app, anon, _cookie) = TestApp::init().with_user(); diff --git a/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-2.snap b/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-2.snap new file mode 100644 index 00000000000..93139121839 --- /dev/null +++ b/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-2.snap @@ -0,0 +1,11 @@ +--- +source: src/tests/routes/crates/list.rs +expression: response.json() +--- +{ + "errors": [ + { + "detail": "parameter category cannot contain a null byte" + } + ] +} diff --git a/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-3.snap b/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-3.snap new file mode 100644 index 00000000000..d4655a0396b --- /dev/null +++ b/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-3.snap @@ -0,0 +1,11 @@ +--- +source: src/tests/routes/crates/list.rs +expression: response.json() +--- +{ + "errors": [ + { + "detail": "parameter all_keywords cannot contain a null byte" + } + ] +} diff --git a/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-4.snap b/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-4.snap new file mode 100644 index 00000000000..f9c4d826d24 --- /dev/null +++ b/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-4.snap @@ -0,0 +1,11 @@ +--- +source: src/tests/routes/crates/list.rs +expression: response.json() +--- +{ + "errors": [ + { + "detail": "parameter keyword cannot contain a null byte" + } + ] +} diff --git a/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-5.snap b/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-5.snap new file mode 100644 index 00000000000..8c44e5b90f9 --- /dev/null +++ b/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes-5.snap @@ -0,0 +1,11 @@ +--- +source: src/tests/routes/crates/list.rs +expression: response.json() +--- +{ + "errors": [ + { + "detail": "parameter letter cannot contain a null byte" + } + ] +} diff --git a/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes.snap b/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes.snap new file mode 100644 index 00000000000..770d93a4f76 --- /dev/null +++ b/src/tests/routes/crates/snapshots/all__routes__crates__list__invalid_params_with_null_bytes.snap @@ -0,0 +1,11 @@ +--- +source: src/tests/routes/crates/list.rs +expression: response.json() +--- +{ + "errors": [ + { + "detail": "parameter q cannot contain a null byte" + } + ] +}