A file’s name says what someone hopes it is; its bytes say what it is.
An upload named invoice.pdf can be a PNG, a .docx is a ZIP archive
inside, and a .csv.gz holds a CSV you cannot see until you open the
gzip. Before a parser starts, you want to know which of these you have,
and whether the file is encrypted or cut short.
zumime answers from both signals in one call and says whether they agree. It needs no other package at run time and no system library, so it gives the same answer on every machine.
net/http sniff cases.application/octet-stream, so an unknown ZIP-based format is still a
ZIP, and an allowlist of "image" accepts every image.mime.types, no Windows
registry, no libmagic: the extension table is generated from IANA’s
registry and Apache httpd’s mime.types.Install zumime from CRAN:
install.packages("zumime")
Or install the development version from GitHub:
# install.packages("pak")
pak::pak("pedrobtz/zumime")
library(zumime)
Take a folder of uploads: a real PDF, a PNG that claims to be a PDF, a compressed CSV and an R data file.
uploads <- tempfile("uploads")
dir.create(uploads)
writeLines("%PDF-1.7", file.path(uploads, "report.pdf"))
writeBin(as.raw(c(0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a)),
file.path(uploads, "invoice.pdf"))
con <- gzfile(file.path(uploads, "sales.csv.gz"), "w")
writeLines(c("region,total", "north,10"), con)
close(con)
saveRDS(mtcars, file.path(uploads, "cars.rds"))
paths <- list.files(uploads, full.names = TRUE)
mime_type() types each file from its bytes and from its name. agree
is FALSE for the PNG named invoice.pdf, and inner names what the
gzip holds, guessed from the .csv before the .gz:
files <- mime_type(paths)
files$path <- basename(files$path)
files[c("path", "type", "by_name", "agree", "inner")]
#> <zumime_result: 4 rows>
#> path type by_name agree inner
#> 1 cars.rds application/x-rds application/x-rds TRUE <NA>
#> 2 invoice.pdf image/png application/pdf FALSE <NA>
#> 3 report.pdf application/pdf application/pdf TRUE <NA>
#> 4 sales.csv.gz application/gzip application/gzip TRUE text/csv
mime_check() asks only the allowlist question, from the bytes alone,
since that is the question you ask because names lie. Of the two files
named .pdf, one is:
setNames(mime_check(paths, "application/pdf"), basename(paths))
#> cars.rds invoice.pdf report.pdf sales.csv.gz
#> FALSE FALSE TRUE FALSE
mime_sniff() runs the standard on bytes in hand, as a browser would on
a response. A text/plain label is one some servers send for anything,
so the standard checks it and finds binary bytes; under nosniff the
label stands:
png <- readBin(file.path(uploads, "invoice.pdf"), "raw", 8)
mime_sniff(png, type = "text/plain")$type
#> [1] "application/octet-stream"
mime_sniff(png, type = "text/plain", no_sniff = TRUE)$type
#> [1] "text/plain"
Types form a hierarchy, so a question about a family covers its members:
docx <- "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
mime_parent(docx)
#> [[1]]
#> [1] "application/zip" "application/octet-stream"
mime_is(docx, "application/zip")
#> [1] TRUE
Every function but zumime_info() starts with mime_. Every type
zumime finds is a canonical name from the registry that mime_types()
lists; a supplied type that stands comes back as its lower-cased
essence.
agree, encrypted and complete
columns, and the incremental sniffer on a stream’s first chunk.The byte answer follows the WHATWG MIME Sniffing Standard and is tested
against Go’s net/http sniff cases; mime_parse() is tested against
the web-platform-tests mimesniff fixtures. Container inspection
follows file-type,
filetype and
mimetype; the extension
table is generated from the IANA media types
registry and Apache
httpd’s mime.types. zumime’s code is written from these references,
not copied from them. The test suite uses Go’s sniff cases and
file-type’s answers as oracles, credited where they are used.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.