1'use strict';
2const { isWindows } = require('../common');
3const assert = require('assert');
4const url = require('url');
5
6{
7  const fileURL = url.pathToFileURL('test/').href;
8  assert.ok(fileURL.startsWith('file:///'));
9  assert.ok(fileURL.endsWith('/'));
10}
11
12{
13  const fileURL = url.pathToFileURL('test\\').href;
14  assert.ok(fileURL.startsWith('file:///'));
15  if (isWindows)
16    assert.ok(fileURL.endsWith('/'));
17  else
18    assert.ok(fileURL.endsWith('%5C'));
19}
20
21{
22  const fileURL = url.pathToFileURL('test/%').href;
23  assert.ok(fileURL.includes('%25'));
24}
25
26{
27  if (isWindows) {
28    // UNC path: \\server\share\resource
29
30    // Missing server:
31    assert.throws(() => url.pathToFileURL('\\\\\\no-server'), {
32      code: 'ERR_INVALID_ARG_VALUE',
33    });
34
35    // Missing share or resource:
36    assert.throws(() => url.pathToFileURL('\\\\host'), {
37      code: 'ERR_INVALID_ARG_VALUE',
38    });
39
40    // Regression test for direct String.prototype.startsWith call
41    assert.throws(() => url.pathToFileURL([
42      '\\\\',
43      { [Symbol.toPrimitive]: () => 'blep\\blop' },
44    ]), {
45      code: 'ERR_INVALID_ARG_TYPE',
46    });
47    assert.throws(() => url.pathToFileURL(['\\\\', 'blep\\blop']), {
48      code: 'ERR_INVALID_ARG_TYPE',
49    });
50    assert.throws(() => url.pathToFileURL({
51      [Symbol.toPrimitive]: () => '\\\\blep\\blop',
52    }), {
53      code: 'ERR_INVALID_ARG_TYPE',
54    });
55
56  } else {
57    // UNC paths on posix are considered a single path that has backslashes:
58    const fileURL = url.pathToFileURL('\\\\nas\\share\\path.txt').href;
59    assert.match(fileURL, /file:\/\/.+%5C%5Cnas%5Cshare%5Cpath\.txt$/);
60  }
61}
62
63{
64  let testCases;
65  if (isWindows) {
66    testCases = [
67      // Lowercase ascii alpha
68      { path: 'C:\\foo', expected: 'file:///C:/foo' },
69      // Uppercase ascii alpha
70      { path: 'C:\\FOO', expected: 'file:///C:/FOO' },
71      // dir
72      { path: 'C:\\dir\\foo', expected: 'file:///C:/dir/foo' },
73      // trailing separator
74      { path: 'C:\\dir\\', expected: 'file:///C:/dir/' },
75      // dot
76      { path: 'C:\\foo.mjs', expected: 'file:///C:/foo.mjs' },
77      // space
78      { path: 'C:\\foo bar', expected: 'file:///C:/foo%20bar' },
79      // question mark
80      { path: 'C:\\foo?bar', expected: 'file:///C:/foo%3Fbar' },
81      // number sign
82      { path: 'C:\\foo#bar', expected: 'file:///C:/foo%23bar' },
83      // ampersand
84      { path: 'C:\\foo&bar', expected: 'file:///C:/foo&bar' },
85      // equals
86      { path: 'C:\\foo=bar', expected: 'file:///C:/foo=bar' },
87      // colon
88      { path: 'C:\\foo:bar', expected: 'file:///C:/foo:bar' },
89      // semicolon
90      { path: 'C:\\foo;bar', expected: 'file:///C:/foo;bar' },
91      // percent
92      { path: 'C:\\foo%bar', expected: 'file:///C:/foo%25bar' },
93      // backslash
94      { path: 'C:\\foo\\bar', expected: 'file:///C:/foo/bar' },
95      // backspace
96      { path: 'C:\\foo\bbar', expected: 'file:///C:/foo%08bar' },
97      // tab
98      { path: 'C:\\foo\tbar', expected: 'file:///C:/foo%09bar' },
99      // newline
100      { path: 'C:\\foo\nbar', expected: 'file:///C:/foo%0Abar' },
101      // carriage return
102      { path: 'C:\\foo\rbar', expected: 'file:///C:/foo%0Dbar' },
103      // latin1
104      { path: 'C:\\fóóbàr', expected: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' },
105      // Euro sign (BMP code point)
106      { path: 'C:\\€', expected: 'file:///C:/%E2%82%AC' },
107      // Rocket emoji (non-BMP code point)
108      { path: 'C:\\�', expected: 'file:///C:/%F0%9F%9A%80' },
109      // UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows)
110      { path: '\\\\nas\\My Docs\\File.doc', expected: 'file://nas/My%20Docs/File.doc' },
111    ];
112  } else {
113    testCases = [
114      // Lowercase ascii alpha
115      { path: '/foo', expected: 'file:///foo' },
116      // Uppercase ascii alpha
117      { path: '/FOO', expected: 'file:///FOO' },
118      // dir
119      { path: '/dir/foo', expected: 'file:///dir/foo' },
120      // trailing separator
121      { path: '/dir/', expected: 'file:///dir/' },
122      // dot
123      { path: '/foo.mjs', expected: 'file:///foo.mjs' },
124      // space
125      { path: '/foo bar', expected: 'file:///foo%20bar' },
126      // question mark
127      { path: '/foo?bar', expected: 'file:///foo%3Fbar' },
128      // number sign
129      { path: '/foo#bar', expected: 'file:///foo%23bar' },
130      // ampersand
131      { path: '/foo&bar', expected: 'file:///foo&bar' },
132      // equals
133      { path: '/foo=bar', expected: 'file:///foo=bar' },
134      // colon
135      { path: '/foo:bar', expected: 'file:///foo:bar' },
136      // semicolon
137      { path: '/foo;bar', expected: 'file:///foo;bar' },
138      // percent
139      { path: '/foo%bar', expected: 'file:///foo%25bar' },
140      // backslash
141      { path: '/foo\\bar', expected: 'file:///foo%5Cbar' },
142      // backspace
143      { path: '/foo\bbar', expected: 'file:///foo%08bar' },
144      // tab
145      { path: '/foo\tbar', expected: 'file:///foo%09bar' },
146      // newline
147      { path: '/foo\nbar', expected: 'file:///foo%0Abar' },
148      // carriage return
149      { path: '/foo\rbar', expected: 'file:///foo%0Dbar' },
150      // latin1
151      { path: '/fóóbàr', expected: 'file:///f%C3%B3%C3%B3b%C3%A0r' },
152      // Euro sign (BMP code point)
153      { path: '/€', expected: 'file:///%E2%82%AC' },
154      // Rocket emoji (non-BMP code point)
155      { path: '/�', expected: 'file:///%F0%9F%9A%80' },
156    ];
157  }
158
159  for (const { path, expected } of testCases) {
160    const actual = url.pathToFileURL(path).href;
161    assert.strictEqual(actual, expected);
162  }
163}
164
165// Test for non-string parameter
166{
167  for (const badPath of [
168    undefined, null, true, 42, 42n, Symbol('42'), NaN, {}, [], () => {},
169    Promise.resolve('foo'),
170    new Date(),
171    new String('notPrimitive'),
172    { toString() { return 'amObject'; } },
173    { [Symbol.toPrimitive]: (hint) => 'amObject' },
174  ]) {
175    assert.throws(() => url.pathToFileURL(badPath), {
176      code: 'ERR_INVALID_ARG_TYPE',
177    });
178  }
179}
180