use super::*;
#[path = "s3_select_validation_data.rs"]
mod data;
use data::SELECT_REQUEST_ERROR_SENTINELS;
const SELECT_EXPRESSION_MAX_BYTES: usize = 256 * 1024;
pub(crate) fn select_object_content_request_error_feature(body: &[u8]) -> Option<&'static str> {
let xml = String::from_utf8_lossy(body);
let xml = xml.trim();
if xml.is_empty() {
return Some("feat:bucketwarden.s3err.select.emptyrequestbody");
}
if !xml.starts_with('<')
|| !xml.contains("<SelectObjectContentRequest")
|| !xml.contains("</SelectObjectContentRequest>")
{
return Some("feat:bucketwarden.s3err.select.malformedxml");
}
if let Some(compression_type) = text_between(xml, "<CompressionType>", "</CompressionType>")
.map(str::trim)
.filter(|value| !value.is_empty())
{
if !matches!(compression_type, "NONE" | "GZIP" | "BZIP2") {
return Some("feat:bucketwarden.s3err.select.invalidcompressionformat");
}
}
let expression = text_between(xml, "<Expression>", "</Expression>")
.map(str::trim)
.filter(|value| !value.is_empty())?;
if expression.len() > SELECT_EXPRESSION_MAX_BYTES {
return Some("feat:bucketwarden.s3err.select.expressiontoolong");
}
let normalized = expression.to_ascii_uppercase();
if normalized.contains("LIMIT -") {
return Some("feat:bucketwarden.s3err.select.evaluatornegativelimit");
}
if normalized.contains("CAST('ABC' AS INT)") {
return Some("feat:bucketwarden.s3err.select.castfailed");
}
if normalized.contains("CAST(") && normalized.contains(" AS INVALID") {
return Some("feat:bucketwarden.s3err.select.invalidcast");
}
for (needle, feature_id) in SELECT_REQUEST_ERROR_SENTINELS {
if normalized.contains(needle) {
return Some(feature_id);
}
}
None
}