Skip to content

feat: content sniff for extensionless text files (Makefile, LICENSE, etc.)#2020

Merged
jolestar merged 1 commit into
mainfrom
content-sniff
Jun 26, 2026
Merged

feat: content sniff for extensionless text files (Makefile, LICENSE, etc.)#2020
jolestar merged 1 commit into
mainfrom
content-sniff

Conversation

@jolestar

Copy link
Copy Markdown
Collaborator

Changes

Add heuristic content sniffing to guess_mime() in the workspace files API. Files without recognized extensions (Makefile, LICENSE, Dockerfile, .gitignore) previously returned application/octet-stream and were treated as binary — now they are correctly detected as text/plain.

Sniff logic (reads first 8000 bytes)

  1. NUL byte → binary (same as git)
  2. Invalid UTF-8 → binary (catches PNG, ELF, and most binary formats)
  3. Non-printable control char ratio >= 30% → binary
  4. Otherwise → text/plain

The sniff only triggers when extension-based mime_guess returns application/octet-stream, so files with known extensions are unaffected.

Verification

  • cargo fmt --all -- --check
  • RUSTFLAGS="-D warnings" cargo check --all-targets
  • cargo test --lib http::workspace_files::tests — 3 tests pass ✅

Add heuristic content sniffing to guess_mime() so that files like
Makefile, LICENSE, Dockerfile, and .gitignore — which have no
recognized extension and previously returned application/octet-stream
— are correctly detected as text/plain.

The sniff reads the first 8000 bytes and checks:
- NUL byte presence (binary signal, same as git)
- UTF-8 validity (catches most binary formats)
- Non-printable control character ratio (>= 30% → binary)

Includes unit tests for text, binary, and threshold boundary cases.
@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holon Ready Ready Preview, Comment Jun 26, 2026 5:57am

@holonbot holonbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: content sniff for extensionless text files

Verdict: Approve with minor suggestions. The sniff heuristic is correct, well-documented, and thoroughly tested. Extension-based detection is preserved since sniffing only triggers on application/octet-stream. Two low-severity notes below; neither is blocking.

What's good

  • Sound heuristic (NUL byte / invalid UTF-8 / ≥30% control-char ratio) matching git and file(1) conventions.
  • Safe fallback to application/octet-stream on any I/O error — no behavior regression on failure.
  • Good test coverage including boundary threshold, UTF-8, PNG/ELF binary headers.

Notes

  1. Blocking I/O in directory listing (warn): guess_mime now does a synchronous open + 8KB read per octet-stream file, and it's called inside the directory-listing loop for every is_file() entry. For large directories with many extensionless files this adds N blocking syscalls in an async handler. Consider skipping the sniff in listings (only sniff on single-file access), wrapping the listing in spawn_blocking, or capping the sniff count.
  2. Single read() underfill (nit): file.read(&mut buf) does one syscall and isn't guaranteed to fill the buffer. For regular files this is fine in practice; file.take(SNIFF_LIMIT_BYTES as u64).read_to_end(&mut buf) would be more deterministic if the full 8KB window matters.
  3. Empty file behavior change (nit): Ok(0) now returns text/plain where it previously returned application/octet-stream. Likely intentional (empty = vacuously text) but worth confirming.

return mime;
}
// Extension-based detection failed; sniff content.
match std::fs::File::open(path) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warn — blocking I/O in directory listing. This synchronous open + read runs per octet-stream entry in the directory-listing loop. For directories with many extensionless files, that's N blocking syscalls inside an async handler. Consider sniffing only on the single-file path, or gating/capping sniffing in listings.

match std::fs::File::open(path) {
Ok(mut file) => {
let mut buf = vec![0u8; SNIFF_LIMIT_BYTES];
match file.read(&mut buf) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — single read() may underfill. Read::read isn't guaranteed to fill the buffer in one call. file.take(SNIFF_LIMIT_BYTES as u64).read_to_end(&mut buf) would be more robust if the full 8KB window matters.

@holonbot

holonbot Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Holon Run Report

@jolestar
jolestar merged commit a3790ba into main Jun 26, 2026
5 checks passed
@jolestar
jolestar deleted the content-sniff branch June 26, 2026 06:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant