Repository: libsql/libsql
File: libsql-server/src/http/user/db_factory.rs (Rust)

Code before:

pub fn namespace_from_headers(
    headers: &HeaderMap,
    disable_default_namespace: bool,
    disable_namespaces: bool,
) -> crate::Result<NamespaceName> {
    if disable_namespaces {
        return Ok(NamespaceName::default());
    }

    let result = if let Some(metadata) = headers.get(NAMESPACE_METADATA_KEY) {
        metadata
            .to_str()
            .map_err(|s| Error::InvalidNamespaceBytes(s))
            .and_then(|ns| NamespaceName::from_string(ns.into()))
    } else {
        headers
            .get("host")
            .ok_or_else(|| Error::InvalidHost("missing host header".into()))
            .and_then(|res| Ok(res.as_bytes()))
            .and_then(|host| {
                std::str::from_utf8(host)
                    .map_err(|_| Error::InvalidHost("host header is not valid UTF-8".into()))
            })
            .and_then(|host_str| split_namespace(host_str))
    };

    if result.is_err() && !disable_default_namespace {
        Ok(NamespaceName::default())
    } else {
        result
    }
}

pub struct MakeConnectionExtractorPath(pub Arc<dyn MakeConnection<Connection = Connection>>);
#[async_trait::async_trait]
impl FromRequestParts<AppState> for MakeConnectionExtractorPath {
    type Rejection = Error;

    async fn from_request_parts(
        parts: &mut Parts,
        state: &AppState,
    ) -> Result<Self, Self::Rejection> {
        let auth = Authenticated::from_request_parts(parts, state).await?;
        let Path((ns, _)) = Path::<(NamespaceName, String)>::from_request_parts(parts, state)
            .await
            .map_err(|e| Error::InvalidPath(e.to_string()))?;
        Ok(Self(
            state
                .namespaces
                .with_authenticated(ns, auth, |ns| ns.db.connection_maker())

Review comment (bug):
The behaviour here changed in two ways:
1. If host header is missing then previously we were always returning Err. New version returns Err only if disbale_default_namespace is true
2. If std::str::from_utf8(host) fails then previously we were always returning Err. New version returns Err only if disable_default_namespace is true

I think it is safer to keep the old behaviour. New behaviour is covering error cases like host error being set to wrong value.

Code after:

pub fn namespace_from_headers(
    headers: &HeaderMap,
    disable_default_namespace: bool,
    disable_namespaces: bool,
) -> crate::Result<NamespaceName> {
    if disable_namespaces {
        return Ok(NamespaceName::default());
    }

    if let Some(from_metadata) = headers.get(NAMESPACE_METADATA_KEY) {
        try_namespace_from_metadata(from_metadata)
    } else if let Some(from_host) = headers.get("host") {
        try_namespace_from_host(from_host, disable_default_namespace)
    } else if !disable_default_namespace {
        Ok(NamespaceName::default())
    } else {
        Err(Error::InvalidHost("missing host header".into()))
    }
}

fn try_namespace_from_host(
    from_host: &axum::http::HeaderValue,
    disable_default_namespace: bool,
) -> Result<NamespaceName, Error> {
    std::str::from_utf8(from_host.as_bytes())
        .map_err(|_| Error::InvalidHost("host header is not valid UTF-8".into()))
        .and_then(|h| match split_namespace(h) {
            Err(_) if !disable_default_namespace => Ok(NamespaceName::default()),
            r => r,
        })
}

fn try_namespace_from_metadata(metadata: &axum::http::HeaderValue) -> Result<NamespaceName, Error> {
    metadata
        .to_str()
        .map_err(|s| Error::InvalidNamespaceBytes(s))
        .and_then(|ns| NamespaceName::from_string(ns.into()))
}

pub struct MakeConnectionExtractorPath(pub Arc<dyn MakeConnection<Connection = Connection>>);
#[async_trait::async_trait]
impl FromRequestParts<AppState> for MakeConnectionExtractorPath {
    type Rejection = Error;

    async fn from_request_parts(
        parts: &mut Parts,
        state: &AppState,
    ) -> Result<Self, Self::Rejection> {
        let auth = Authenticated::from_request_parts(parts, state).await?;
        let Path((ns, _)) = Path::<(NamespaceName, String)>::from_request_parts(parts, state)