diff --git a/Lib/fontTools/ufoLib/filenames.py b/Lib/fontTools/ufoLib/filenames.py index ec33b452a..baf22076c 100644 --- a/Lib/fontTools/ufoLib/filenames.py +++ b/Lib/fontTools/ufoLib/filenames.py @@ -3,11 +3,88 @@ User name to file name conversion. This was taken from the UFO 3 spec. """ -illegalCharacters = r"\" * + / : < > ? [ \ ] | \0".split(" ") -illegalCharacters += [chr(i) for i in range(1, 32)] -illegalCharacters += [chr(0x7F)] -reservedFileNames = "CON PRN AUX CLOCK$ NUL A:-Z: COM1".lower().split(" ") -reservedFileNames += "LPT1 LPT2 LPT3 COM2 COM3 COM4".lower().split(" ") +# Restrictions are taken mostly from +# https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file#naming-conventions. +# +# 1. Integer value zero, sometimes referred to as the ASCII NUL character. +# 2. Characters whose integer representations are in the range 1 to 31, +# inclusive. +# 3. Various characters that (mostly) Windows and POSIX-y filesystems don't +# allow, plus "(" and ")", as per the specification. +illegalCharacters = { + "\x00", + "\x01", + "\x02", + "\x03", + "\x04", + "\x05", + "\x06", + "\x07", + "\x08", + "\t", + "\n", + "\x0b", + "\x0c", + "\r", + "\x0e", + "\x0f", + "\x10", + "\x11", + "\x12", + "\x13", + "\x14", + "\x15", + "\x16", + "\x17", + "\x18", + "\x19", + "\x1a", + "\x1b", + "\x1c", + "\x1d", + "\x1e", + "\x1f", + '"', + "*", + "+", + "/", + ":", + "<", + ">", + "?", + "[", + "\\", + "]", + "(", + ")", + "|", + "\x7f", +} +reservedFileNames = { + "aux", + "clock$", + "com1", + "com2", + "com3", + "com4", + "com5", + "com6", + "com7", + "com8", + "com9", + "con", + "lpt1", + "lpt2", + "lpt3", + "lpt4", + "lpt5", + "lpt6", + "lpt7", + "lpt8", + "lpt9", + "nul", + "prn", +} maxFileNameLength = 255 diff --git a/Tests/ufoLib/filenames_test.py b/Tests/ufoLib/filenames_test.py index a9415a1c2..bad413530 100644 --- a/Tests/ufoLib/filenames_test.py +++ b/Tests/ufoLib/filenames_test.py @@ -29,6 +29,16 @@ class TestFilenames(unittest.TestCase): self.assertEqual(userNameToFileName("con.alt"), "_con.alt") self.assertEqual(userNameToFileName("alt.con"), "alt._con") + self.assertEqual( + # Test output for ASCII range (up until 0x7F), except for illegal + # characters. + userNameToFileName("".join([chr(i) for i in range(0, 0x80)])), + "________________________________" + " !_#$%&'____,-._0123456789_;_=__" + "@A_B_C_D_E_F_G_H_I_J_K_L_M_N_O_P_Q_R_S_T_U_V_W_X_Y_Z_" + "___^_`abcdefghijklmnopqrstuvwxyz{_}~_", + ) + def test_userNameToFileName_ValueError(self): with self.assertRaises(ValueError): userNameToFileName(b"a")