| 
 | 1 | +'use strict';  | 
 | 2 | + | 
 | 3 | +const common = require('../common');  | 
 | 4 | +const assert = require('assert');  | 
 | 5 | +const path = require('path');  | 
 | 6 | + | 
 | 7 | +if (!common.isWindows) {  | 
 | 8 | + common.skip('Windows only');  | 
 | 9 | +}  | 
 | 10 | + | 
 | 11 | +const normalizeDeviceNameTests = [  | 
 | 12 | + { input: 'CON', expected: 'CON' },  | 
 | 13 | + { input: 'con', expected: 'con' },  | 
 | 14 | + { input: 'CON:', expected: '.\\CON:.' },  | 
 | 15 | + { input: 'con:', expected: '.\\con:.' },  | 
 | 16 | + { input: 'CON:.', expected: '.\\CON:.' },  | 
 | 17 | + { input: 'coN:', expected: '.\\coN:.' },  | 
 | 18 | + { input: 'LPT9.foo', expected: 'LPT9.foo' },  | 
 | 19 | + { input: 'COM9:', expected: '.\\COM9:.' },  | 
 | 20 | + { input: 'COM9.', expected: '.\\COM9.' },  | 
 | 21 | + { input: 'C:COM9', expected: 'C:COM9' },  | 
 | 22 | + { input: 'C:\\COM9', expected: 'C:\\COM9' },  | 
 | 23 | + { input: 'CON:./foo', expected: '.\\CON:foo' },  | 
 | 24 | + { input: 'CON:/foo', expected: '.\\CON:foo' },  | 
 | 25 | + { input: 'CON:../foo', expected: '.\\CON:..\\foo' },  | 
 | 26 | + { input: 'CON:/../foo', expected: '.\\CON:..\\foo' },  | 
 | 27 | + { input: 'CON:./././foo', expected: '.\\CON:foo' },  | 
 | 28 | + { input: 'CON:..', expected: '.\\CON:..' },  | 
 | 29 | + { input: 'CON:..\\', expected: '.\\CON:..\\' },  | 
 | 30 | + { input: 'CON:..\\..', expected: '.\\CON:..\\..' },  | 
 | 31 | + { input: 'CON:..\\..\\', expected: '.\\CON:..\\..\\' },  | 
 | 32 | + { input: 'CON:..\\..\\foo', expected: '.\\CON:..\\..\\foo' },  | 
 | 33 | + { input: 'CON:..\\..\\foo\\', expected: '.\\CON:..\\..\\foo\\' },  | 
 | 34 | + { input: 'CON:..\\..\\foo\\bar', expected: '.\\CON:..\\..\\foo\\bar' },  | 
 | 35 | + { input: 'CON:..\\..\\foo\\bar\\', expected: '.\\CON:..\\..\\foo\\bar\\' },  | 
 | 36 | + { input: 'COM1:a:b:c', expected: '.\\COM1:a:b:c' },  | 
 | 37 | + { input: 'COM1:a:b:c/', expected: '.\\COM1:a:b:c\\' },  | 
 | 38 | + { input: 'c:lpt1', expected: 'c:lpt1' },  | 
 | 39 | + { input: 'c:\\lpt1', expected: 'c:\\lpt1' },  | 
 | 40 | + | 
 | 41 | + // Reserved device names with path traversal  | 
 | 42 | + { input: 'CON:.\\..\\..\\foo', expected: '.\\CON:..\\..\\foo' },  | 
 | 43 | + { input: 'PRN:.\\..\\bar', expected: '.\\PRN:..\\bar' },  | 
 | 44 | + { input: 'AUX:/../../baz', expected: '.\\AUX:..\\..\\baz' },  | 
 | 45 | + | 
 | 46 | + { input: 'COM1:', expected: '.\\COM1:.' },  | 
 | 47 | + { input: 'COM9:', expected: '.\\COM9:.' },  | 
 | 48 | + { input: 'COM1:.\\..\\..\\foo', expected: '.\\COM1:..\\..\\foo' },  | 
 | 49 | + { input: 'LPT1:', expected: '.\\LPT1:.' },  | 
 | 50 | + { input: 'LPT9:', expected: '.\\LPT9:.' },  | 
 | 51 | + { input: 'LPT1:.\\..\\..\\foo', expected: '.\\LPT1:..\\..\\foo' },  | 
 | 52 | + { input: 'LpT5:/another/path', expected: '.\\LpT5:another\\path' },  | 
 | 53 | + | 
 | 54 | + { input: 'C:\\foo', expected: 'C:\\foo' },  | 
 | 55 | + { input: 'D:bar', expected: 'D:bar' },  | 
 | 56 | + | 
 | 57 | + { input: 'CON', expected: 'CON' },  | 
 | 58 | + { input: 'CON.TXT', expected: 'CON.TXT' },  | 
 | 59 | + { input: 'COM10:', expected: '.\\COM10:' },  | 
 | 60 | + { input: 'LPT10:', expected: '.\\LPT10:' },  | 
 | 61 | + { input: 'CONNINGTOWER:', expected: '.\\CONNINGTOWER:' },  | 
 | 62 | + { input: 'AUXILIARYDEVICE:', expected: '.\\AUXILIARYDEVICE:' },  | 
 | 63 | + { input: 'NULLED:', expected: '.\\NULLED:' },  | 
 | 64 | + { input: 'PRNINTER:', expected: '.\\PRNINTER:' },  | 
 | 65 | + | 
 | 66 | + { input: 'CON:\\..\\..\\windows\\system32', expected: '.\\CON:..\\..\\windows\\system32' },  | 
 | 67 | + { input: 'PRN:.././../etc/passwd', expected: '.\\PRN:..\\..\\etc\\passwd' },  | 
 | 68 | + | 
 | 69 | + // Test with trailing slashes  | 
 | 70 | + { input: 'CON:\\', expected: '.\\CON:.\\' },  | 
 | 71 | + { input: 'COM1:\\foo\\bar\\', expected: '.\\COM1:foo\\bar\\' },  | 
 | 72 | + | 
 | 73 | + // Test cases from original vulnerability reports or similar scenarios  | 
 | 74 | + { input: 'COM1:.\\..\\..\\foo.js', expected: '.\\COM1:..\\..\\foo.js' },  | 
 | 75 | + { input: 'LPT1:.\\..\\..\\another.txt', expected: '.\\LPT1:..\\..\\another.txt' },  | 
 | 76 | + | 
 | 77 | + // Paths with device names not at the beginning  | 
 | 78 | + { input: 'C:\\CON', expected: 'C:\\CON' },  | 
 | 79 | + { input: 'C:\\path\\to\\COM1:', expected: 'C:\\path\\to\\COM1:' },  | 
 | 80 | + | 
 | 81 | + // Device name followed by multiple colons  | 
 | 82 | + { input: 'CON::', expected: '.\\CON::' },  | 
 | 83 | + { input: 'COM1:::foo', expected: '.\\COM1:::foo' },  | 
 | 84 | + | 
 | 85 | + // Device name with mixed path separators  | 
 | 86 | + { input: 'AUX:/foo\\bar/baz', expected: '.\\AUX:foo\\bar\\baz' },  | 
 | 87 | +];  | 
 | 88 | + | 
 | 89 | +for (const { input, expected } of normalizeDeviceNameTests) {  | 
 | 90 | + const actual = path.win32.normalize(input);  | 
 | 91 | + assert.strictEqual(actual, expected,  | 
 | 92 | + `path.win32.normalize(${JSON.stringify(input)}) === ${JSON.stringify(expected)}, but got ${JSON.stringify(actual)}`);  | 
 | 93 | +}  | 
 | 94 | + | 
 | 95 | +assert.strictEqual(path.win32.normalize('CON:foo/../bar'), '.\\CON:bar');  | 
 | 96 | + | 
 | 97 | +// This should NOT be prefixed because 'c:' is treated as a drive letter.  | 
 | 98 | +assert.strictEqual(path.win32.normalize('c:COM1:'), 'c:COM1:');  | 
0 commit comments