Sample Filename edge cases test files
What are filename edge cases?
File names that break file handling on some system: reserved Windows names such as CON, characters Windows forbids, right-to-left overrides, Unicode in NFC and NFD forms, trailing dots, double extensions and names past length limits. Each file here has one.
26 free filename edge cases test files for QA and developers: .hidden-leading-dot, 🦊-emoji-name.txt, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa…
| File | Format | Size | What it catches |
.hidden-leading-dot | TXT | 142 bytes | Leading dot, no extension - hidden on POSIX. |
🦊-emoji-name.txt | TXT | 142 bytes | Emoji (4-byte UTF-8, outside the BMP) in the filename. |
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt | TXT | 142 bytes | A 204-character filename. |
ampersand&name.txt | TXT | 142 bytes | An ampersand in the filename. |
archive.tar.gz | TXT | 142 bytes | A legitimate compound extension. |
backslash\name.txt | TXT | 142 bytes | A backslash in the filename. |
CON.txt | TXT | 142 bytes | A Windows reserved device name. |
dots.in.the.name.txt | TXT | 142 bytes | Multiple dots. |
double"quote.txt | TXT | 142 bytes | An ASCII double quote in the filename. |
hash#name.txt | TXT | 142 bytes | A hash character in the filename. |
invoicegnp.txt | TXT | 142 bytes | Contains a right-to-left override (U+202E) so it renders as "invoicetxt.png". |
nfc-café.txt | TXT | 142 bytes | Precomposed Unicode (NFC): é is a single code point U+00E9. |
nfd-café.txt | TXT | 142 bytes | Decomposed Unicode (NFD): é is e + U+0301 combining acute. |
no-extension-at-all | TXT | 142 bytes | No extension at all. |
NUL.txt | TXT | 142 bytes | Another Windows reserved device name. |
percent%20encoded.txt | TXT | 142 bytes | A literal "%20" sequence in the filename. |
plus+name.txt | TXT | 142 bytes | A plus sign in the filename. |
question?name.txt | TXT | 142 bytes | A question mark in the filename. |
report.pdf.exe | TXT | 142 bytes | Double extension where the real extension is last. |
semicolon;name.txt | TXT | 142 bytes | A semicolon in the filename. |
single'quote.txt | TXT | 142 bytes | An ASCII single quote in the filename. |
spaces in name.txt | TXT | 142 bytes | Spaces in the filename. |
trailing-dot.txt. | TXT | 142 bytes | A trailing dot after the extension. |
UPPERCASE.TXT | TXT | 142 bytes | All-caps name and extension. |
файл-кириллица.txt | TXT | 142 bytes | Cyrillic characters in the filename. |
日本語ファイル名.txt | TXT | 142 bytes | Japanese characters in the filename. |
Handle it in code
Each snippet below was run against this exact file with Python 3.14.4 and Node 24.15.0 when this page was built, and the output shown is what it printed.
Python
Check uploaded file names for invisible direction controls and executable double extensions before you store or display them.
import unicodedata
BIDI = {"LRE", "RLE", "PDF", "LRO", "RLO", "LRI", "RLI", "FSI", "PDI"}
RUNS = {"exe", "scr", "bat", "cmd", "com", "js", "vbs", "ps1", "msi", "jar", "lnk"}
def check_name(name):
problems = []
if any(unicodedata.bidirectional(c) in BIDI for c in name):
problems.append("contains an invisible direction control")
parts = name.lower().split(".")
if len(parts) > 2 and parts[-1] in RUNS:
problems.append(f"double extension .{parts[-2]}.{parts[-1]}")
return problems or ["ok"]
for name in ["invoice\u202Egnp.txt", "report.pdf.exe", "notes.txt"]:
print(ascii(name), check_name(name))Run against this file, it prints: 'invoice\u202egnp.txt' ['contains an invisible direction control']
'report.pdf.exe' ['double extension .pdf.exe']
'notes.txt' ['ok']
Node.js
The same checks in JavaScript.
const BIDI = /[\u202A-\u202E\u2066-\u2069]/;
const RUNS = new Set(['exe', 'scr', 'bat', 'cmd', 'com', 'js', 'vbs', 'ps1', 'msi', 'jar', 'lnk']);
function checkName(name) {
const problems = [];
if (BIDI.test(name)) problems.push('contains an invisible direction control');
const parts = name.toLowerCase().split('.');
if (parts.length > 2 && RUNS.has(parts.at(-1))) problems.push('double extension .' + parts.at(-2) + '.' + parts.at(-1));
return problems.length ? problems : ['ok'];
}
// Print control characters as escapes, so the output cannot reorder itself.
const show = (s) => s.replace(/[\u202A-\u202E\u2066-\u2069]/g, (c) => '\\u' + c.charCodeAt(0).toString(16));
for (const name of ['invoice\u202Egnp.txt', 'report.pdf.exe', 'notes.txt']) {
console.log(show(name), checkName(name));
}Run against this file, it prints: invoice\u202egnp.txt [ 'contains an invisible direction control' ]
report.pdf.exe [ 'double extension .pdf.exe' ]
notes.txt [ 'ok' ]