actix/actix-web · error · io::Error

Provided path has no filename

Error message

Provided path has no filename

What it means

An io::Error of kind InvalidInput with message "Provided path has no filename" is returned by get_content_type_and_disposition (named.rs:100) when Path::file_name() returns None. This happens for paths that conceptually have no final component - a trailing "..", the root "/" (or "/"), or an empty path. NamedFile::from_file (named.rs:190) propagates it because it cannot derive a Content-Type or Content-Disposition filename.

Solutions

  1. Validate that the path has a file name before opening: ensure path.file_name().is_some().
  2. Reject or normalize paths containing trailing ".." or that equal the root.
  3. Sanitize user-supplied path segments and never let them reach the filesystem unsanitized.

Example fix

// before
NamedFile::open_async("/").await // Err: no filename

// after
let p = std::path::Path::new("/srv/site/index.html");
assert!(p.file_name().is_some());
NamedFile::open_async(p).await
Defensive patterns

Strategy: validation

Validate before calling

// Reject paths without a filename before opening
fn open_named(p: impl AsRef<Path>) -> Result<NamedFile, io::Error> {
    let p = p.as_ref();
    if p.file_name().is_none() {
        return Err(io::Error::new(io::ErrorKind::InvalidInput, "no filename"));
    }
    NamedFile::open(p)
}

Type guard

fn has_filename(p: &Path) -> bool {
    p.file_name().map(|n| !n.is_empty()).unwrap_or(false)
}

Try / catch

match NamedFile::open_async(&path).await {
    Ok(f) => Ok(f.into_response(&req)),
    Err(e) if e.kind() == io::ErrorKind::InvalidInput =>
        HttpResponse::BadRequest().finish(),
    Err(_) => HttpResponse::InternalServerError().finish(),
}

Prevention

When it happens

Trigger: Calling NamedFile::open/open_async or from_file with a path like "/", "", "..", or "/dir/.." - anything where path.file_name() is None. Also hit indirectly via Files directory service if a computed path resolves to such a form.

Common situations: Dynamic path construction joining user input that collapses to root; serving a path that is itself a directory specifier; misconfigured static-file root; symbolic-link resolution landing on "/".

Related errors


AI-assisted analysis of actix/actix-web@7ae209e4a4 (2026-08-09). Data as JSON: /api/errors/92c1e49fb15e09db. Report an issue: GitHub.

Appendix: source

Thrown at actix-files/src/named.rs:96

    pub(crate) flags: Flags,
    pub(crate) status_code: StatusCode,
    pub(crate) content_type: Mime,
    pub(crate) content_disposition: ContentDisposition,
    pub(crate) encoding: Option<ContentEncoding>,
    pub(crate) read_mode_threshold: u64,
}

pub(crate) use std::fs::File;

use super::chunked;

pub(crate) fn get_content_type_and_disposition(
    path: &Path,
) -> Result<(mime::Mime, ContentDisposition), io::Error> {
    let filename = match path.file_name() {
        Some(name) => name.to_string_lossy(),
        None => {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Provided path has no filename",
            ));
        }
    };

    let ct = mime_guess::from_path(path).first_or_octet_stream();

    let disposition = match ct.type_() {
        mime::IMAGE | mime::TEXT | mime::AUDIO | mime::VIDEO => DispositionType::Inline,
        mime::APPLICATION => match ct.subtype() {
            mime::JAVASCRIPT | mime::JSON => DispositionType::Inline,
            name if name == "wasm" || name == "xhtml" => DispositionType::Inline,
            _ => DispositionType::Attachment,
        },
        _ => DispositionType::Attachment,
    };

View on GitHub (pinned to 7ae209e4a4)