bucketwarden-server 0.1.0

BucketWarden storage server runtime.
Documentation
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
}