Unverified Commit f2948f8e authored by 0xdx2's avatar 0xdx2 Committed by GitHub
Browse files

fix(ops): enhance extract_host to return host from URI if available (#431)



* fix(ops): enhance extract_host to return host from URI if available

* Update crates/s3s/src/ops/mod.rs

Co-authored-by: default avatarCopilot <175728472+Copilot@users.noreply.github.com>

* Update crates/s3s/src/ops/tests.rs

Co-authored-by: default avatarCopilot <175728472+Copilot@users.noreply.github.com>

* fix(ops): improve extract_host to prioritize HOST header over URI host

* fix(ops): update extract_host to utilize http crate and enhance URI handling

* Update crates/s3s/src/ops/mod.rs

Co-authored-by: default avatarCopilot <175728472+Copilot@users.noreply.github.com>

* fix(tests): update extract_host_from_uri tests to assert expected host values

* fix(ops): enhance extract_host to correctly format host with port when applicable

* fix(ops): simplify extract_host to format host with port only when necessary

* fix

---------

Co-authored-by: default avatardamon <damonxue2@gmail.com>
Co-authored-by: default avatarCopilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: default avatarNugine <nugine@foxmail.com>
parent cb67432c
Loading
Loading
Loading
Loading
+23 −4
Original line number Diff line number Diff line
@@ -104,11 +104,30 @@ fn unknown_operation() -> S3Error {
    S3Error::with_message(S3ErrorCode::NotImplemented, "Unknown operation")
}

fn extract_http2_authority(req: &Request) -> Option<&str> {
    if matches!(req.version, ::http::Version::HTTP_2 | ::http::Version::HTTP_3) {
        if let Some(authority) = req.uri.authority() {
            return Some(authority.as_str());
        }
    }
    None
}

fn extract_host(req: &Request) -> S3Result<Option<String>> {
    let Some(val) = req.headers.get(crate::header::HOST) else { return Ok(None) };
    // First try to get from Host header.
    if let Some(val) = req.headers.get(crate::header::HOST) {
        let on_err = |e| s3_error!(e, InvalidRequest, "invalid header: Host: {val:?}");
        let host = val.to_str().map_err(on_err)?;
    Ok(Some(host.into()))
        return Ok(Some(host.into()));
    }

    // For HTTP/2 and HTTP/3, the Host header is replaced by :authority pseudo-header.
    // https://github.com/hyperium/hyper/discussions/2435
    if let Some(authority) = extract_http2_authority(req) {
        return Ok(Some(authority.into()));
    }

    Ok(None)
}

fn is_socket_addr_or_ip_addr(host: &str) -> bool {
+1 −1
Original line number Diff line number Diff line
@@ -268,7 +268,7 @@ impl SignatureContext<'_> {
                // HTTP/2 replaces `host` header with `:authority`
                // but `:authority` is not in the request headers
                // so we need to add it back if `host` is in the signed headers
                if name == "host" && self.req_version == ::http::Version::HTTP_2 {
                if name == "host" && matches!(self.req_version, ::http::Version::HTTP_2 | ::http::Version::HTTP_3) {
                    if let Some(authority) = self.req_uri.authority() {
                        return Some(authority.as_str());
                    }
+66 −0
Original line number Diff line number Diff line
@@ -62,3 +62,69 @@ fn error_custom_headers() {
        )
    );
}

#[test]
fn extract_host_from_uri() {
    use crate::http::Request;
    use crate::ops::extract_host;

    let mut req = Request::from(
        hyper::Request::builder()
            .method(Method::GET)
            .version(::http::Version::HTTP_2)
            .uri("https://test.example.com:9001/rust.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20251213T084305Z&X-Amz-SignedHeaders=host&X-Amz-Credential=rustfsadmin%2F20251213%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Expires=3600&X-Amz-Signature=57133ee54dab71c00a10106c33cde2615b301bd2cf00e2439f3ddb4bc999ec66")
            .body(Body::empty())
            .unwrap(),
    );

    let host = extract_host(&req).unwrap();
    assert_eq!(host, Some("test.example.com:9001".to_string()));

    req.version = ::http::Version::HTTP_11;
    let host = extract_host(&req).unwrap();
    assert_eq!(host, None);

    req.version = ::http::Version::HTTP_3;
    let host = extract_host(&req).unwrap();
    assert_eq!(host, Some("test.example.com:9001".to_string()));

    let mut req = Request::from(
        hyper::Request::builder()
            .version(::http::Version::HTTP_10)
            .method(Method::GET)
            .uri("http://another.example.org/resource")
            .body(Body::empty())
            .unwrap(),
    );
    let host = extract_host(&req).unwrap();
    assert_eq!(host, None);

    req.version = ::http::Version::HTTP_2;
    let host = extract_host(&req).unwrap();
    assert_eq!(host, Some("another.example.org".to_string()));

    req.version = ::http::Version::HTTP_3;
    let host = extract_host(&req).unwrap();
    assert_eq!(host, Some("another.example.org".to_string()));

    let req = Request::from(
        hyper::Request::builder()
            .method(Method::GET)
            .uri("/no/host/header")
            .header("Host", "header.example.com:8080")
            .body(Body::empty())
            .unwrap(),
    );
    let host = extract_host(&req).unwrap();
    assert_eq!(host, Some("header.example.com:8080".to_string()));

    let req = Request::from(
        hyper::Request::builder()
            .method(Method::GET)
            .uri("/no/host/header")
            .body(Body::empty())
            .unwrap(),
    );
    let host = extract_host(&req).unwrap();
    assert_eq!(host, None);
}