70 Bytes Untouched, and the Type Changed 13 Times
Let me open with a scene that flatters nobody. I took one PNG file header, 70 bytes of it, and copied it under fi_x.png, fi_x.jpg, fi_x.gif and onward until the pile reached 18 extensions, without altering a single byte inside any of them. Each copy went in through the real file picker, the way a person drops files onto the page. The MIME type column answered with 13 different spellings, and the rest fell into unknown. Those 70 bytes spell \x89PNG\r\n\x1a\n right at the front, the PNG magic number that any format book recites before it says anything else, and the attitude of that column toward it is to not look.
The trouble is not that the display reads oddly. We use a page like this as a file inspector, and that phrase smuggles in two assumptions: that it read the file, and that the type printed beside the name was inferred from what is inside. Hold both and you start branching downstream: render the row when the type claims image, send it to an unpacker when the type claims archive, hand it back to a person when the type claims unknown. What the MIME type column of file-info does is transcribe the string the browser produces from the name. The extension is the only input, and the magic number never takes part.
Laying the 18 extensions out flat
The complete comparison is below. In each pair of columns the left is an extension I created by renaming, the right is the string that cell showed. The only variable anywhere was the handful of letters at the tail of the name, so every difference in this table is the name talking, not the file.
| Extension | The MIME type column | Extension | The MIME type column |
|---|---|---|---|
.png | image/png | .json | application/json |
.jpg | image/jpeg | .csv | text/csv |
.jpeg | image/jpeg | .bin | application/octet-stream |
.txt | text/plain | .heic | unknown |
.zip | application/x-zip-compressed | .log | unknown |
.pdf | application/pdf | .7z | unknown |
.md | text/markdown | .dat | unknown |
.gif | image/gif | .xyz9 | unknown |
.webp | image/webp | .html | text/html |
13 types plus 5 unknowns fill 18 rows. .jpg and .jpeg came back with the same string, so the mapping carries merges of its own. The ones that resolved to nothing are .heic, .log, .7z, .dat and .xyz9, and only the last is rare: iPhone photos and log files are everyday objects, this machine simply has no entry for them. Run the same batch of names elsewhere and which ones land on unknown can move, one reason I use the file info reader as a comparison bench rather than a verdict.
When the magic number and the extension disagree, the extension wins
Renaming in one direction cannot settle the reading. What pins the rule down is twisting the two fingerprints against each other on purpose: the content of one format under the name of another. I built four of those, two each way, and the type column followed the name every time.
The byte groups below are what I wrote at the head of each file, and the size is what the page echoed back beside it.
JPEG header FF D8 FF E0 named fi_photo.png → image/png 22 B ZIP header 50 4B 03 04 named fi_archive.jpg → image/jpeg 39 B GIF header 47 49 46 38 39 61 renamed .zip → application/x-zip-compressed 10 B PNG header 89 50 4E 47 0D 0A renamed .gif → image/gif 70 B
Not one byte of content changed across those rows, only the names did. That column has exactly one origin in the template, line 139: type: f.type || '未知'. It copies the string sitting on the File object straight into the table cell and stops there. The interfaces that would actually touch the bytes, FileReader, readAs, ArrayBuffer, DataView, each appear zero times in the whole template, so there is no sniffing branch to test, no shallow sniffing, no sniffing behind an option. Naming that column MIME type is generous; naming it the string the browser gives per extension is accurate, since renaming a file really does rename its nature here.
How much of the file it actually reads
Spreading the template out makes the shape plain. Line 136 puts the file object into an array, and lines 137 through 140 take four fields out of it, name, size, type and lastModified. After that the object is never read again. The table holds four columns not because the design shows restraint but because the input is exactly that wide, and I checked against that standard for several things people quietly assume are present.
- Line count, character count, word count, hash values, encoding detection, image width and height, creation time, permissions, the full path: every one of those hits zero times in the template. Where no column exists there is no fallback to describe either, so asking why the MD5 cell is empty is asking after a cell nobody built.
- There is no format whitelist.
accept=appears zero times, so any extension walks into the list. There is no size ceiling either: I fed in a sparse file of 6442450944 bytes and it listed without complaint, showing6.00 GB, with no rejection branch and no degraded notice. - There is no count ceiling and no de-duplication. I fed the same 10 byte file 100 times and got 100 rows with a total of
1000 B, and not one notice about a repeated name. Using this page to ask whether a batch repeats itself is a wasted errand. - Feed it a whole directory and you get a single row:
0 Bbeside unknown. It does not walk into the folder and does not add the children together, so this is not how you total a folder. On that path it hands you a zero.
The size column runs on 1024 while the label reads KB
The thresholds live on lines 163 through 166 and each is a power of 1024. The constant 1000 appears zero times in the template and the letters KiB appear zero times, yet the labels read KB / MB / GB, and that mismatch is the whole story of the column. A disk the vendor sold as 1 GB holds 1000000000 bytes, which this page prints as 953.67 MB, short of 1073741824 by 73741824 bytes, and 1000000 bytes prints as 976.6 KB while still sitting in the kilobyte band. The step point is 1024 too, so 1023 still reads 1023 B instead of tipping upward.
Decimal places come in three bands: the byte band shows none, the kilobyte band shows one, the megabyte and gigabyte bands show two. The rounding inside toFixed makes the printed figure a little larger than the file almost every time, which is how 2047 becomes 2.0 KB and 1048575 becomes 1024.0 KB, one byte shy of the next band and still wearing the previous label. Line 166 is the last return in that function, so there is no terabyte band: a file of 1099511627776 bytes reports 1024.00 GB. Compare it with any listing that divides sizes by a thousand instead and the two disagree on purpose, the gap widening as the file grows. Writing MiB, GiB and TiB in your own notes is the cheap remedy.
Three details people ask about while the page is still open
Q: Why does my .zip show something other than application/zip?
Because that column transcribes and never normalises. Chrome on this Windows machine reports .zip as application/x-zip-compressed, and .tar.gz is judged only by its final segment, so it arrives as application/x-gzip. Hand it application/zip explicitly and the cell still displays precisely what you gave it, uncorrected. Move to another browser and the string itself may move, which is worth knowing before you compare two screenshots and hunt for a bug.
Q: When neither the type nor the time can be read, what goes in the cell?
The columns do not agree with one another. The type only reaches the fallback printed as 未知 when it is an empty string, which is why application/octet-stream displays as itself instead of being swept into unknown. A lastModified of zero gets no default check and renders as 1970-01-01 08:00:00, an epoch date rather than a blank. A file of no bytes shows 0 B. There is no spelling for empty in this table, and three separate notions of missing in one row.
Q: Does the exported text follow the sort on screen?
It does not. Line 245 walks the original array, and the sorted copy exists only to paint rows. After I sorted 32 files by modification time in ascending order the first row on screen was rn_01.png, while the exported row carrying the first index was still mx_gifmagic.zip. The batch of 16 files exported 23 lines, 1098 characters and 1208 bytes, ending without a trailing newline. If you pipe that text into another script, trust insertion order rather than the order you just looked at.