Test files › Filename edge cases

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…

FileFormatSizeWhat it catches
.hidden-leading-dotTXT142 bytesLeading dot, no extension - hidden on POSIX.
🦊-emoji-name.txtTXT142 bytesEmoji (4-byte UTF-8, outside the BMP) in the filename.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txtTXT142 bytesA 204-character filename.
ampersand&name.txtTXT142 bytesAn ampersand in the filename.
archive.tar.gzTXT142 bytesA legitimate compound extension.
backslash\name.txtTXT142 bytesA backslash in the filename.
CON.txtTXT142 bytesA Windows reserved device name.
dots.in.the.name.txtTXT142 bytesMultiple dots.
double"quote.txtTXT142 bytesAn ASCII double quote in the filename.
hash#name.txtTXT142 bytesA hash character in the filename.
invoice‮gnp.txtTXT142 bytesContains a right-to-left override (U+202E) so it renders as "invoicetxt.png".
nfc-café.txtTXT142 bytesPrecomposed Unicode (NFC): é is a single code point U+00E9.
nfd-café.txtTXT142 bytesDecomposed Unicode (NFD): é is e + U+0301 combining acute.
no-extension-at-allTXT142 bytesNo extension at all.
NUL.txtTXT142 bytesAnother Windows reserved device name.
percent%20encoded.txtTXT142 bytesA literal "%20" sequence in the filename.
plus+name.txtTXT142 bytesA plus sign in the filename.
question?name.txtTXT142 bytesA question mark in the filename.
report.pdf.exeTXT142 bytesDouble extension where the real extension is last.
semicolon;name.txtTXT142 bytesA semicolon in the filename.
single'quote.txtTXT142 bytesAn ASCII single quote in the filename.
spaces in name.txtTXT142 bytesSpaces in the filename.
trailing-dot.txt.TXT142 bytesA trailing dot after the extension.
UPPERCASE.TXTTXT142 bytesAll-caps name and extension.
файл-кириллица.txtTXT142 bytesCyrillic characters in the filename.
日本語ファイル名.txtTXT142 bytesJapanese 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' ]

← All test files