Skip to content

Commit 332d799

Browse files
committed
Parser: handle file:// and file:/// correctly
1 parent ac34ab5 commit 332d799

File tree

4 files changed

+88
-23
lines changed

4 files changed

+88
-23
lines changed

CHANGELOG.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
unreleased:
2+
fixed bugs:
3+
- >-
4+
GH-133 Fixed a bug where slashes in the `file` protocol are handled
5+
incorrectly
6+
17
3.0.2:
28
date: 2021-07-14
39
fixed bugs:

parser/index.js

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424

2525
const ReplacementTracker = require('./replacement-tracker'),
2626

27-
REGEX_EXTRACT_VARS = /{{[^{}]*[.:/?#@&\]][^{}]*}}/g,
27+
REGEX_ALL_BACKSLASHES = /\\/g,
28+
REGEX_LEADING_SLASHES = /^\/+/,
29+
REGEX_ALL_VARIABLES = /{{[^{}]*[.:/?#@&\]][^{}]*}}/g,
2830

2931
HASH_SEPARATOR = '#',
3032
PATH_SEPARATOR = '/',
@@ -35,8 +37,10 @@ const ReplacementTracker = require('./replacement-tracker'),
3537
PROTOCOL_SEPARATOR = '://',
3638
AUTH_SEGMENTS_SEPARATOR = ':',
3739
QUERY_SEGMENTS_SEPARATOR = '&',
38-
PROTOCOL_SEPARATOR_WITH_BACKSLASH = ':\\\\',
3940

41+
E = '',
42+
STRING = 'string',
43+
FILE_PROTOCOL = 'file',
4044
SAFE_REPLACE_CHAR = '_',
4145
CLOSING_SQUARE_BRACKET = ']',
4246
URL_PROPERTIES_ORDER = ['protocol', 'auth', 'host', 'port', 'path', 'query', 'hash'];
@@ -52,7 +56,7 @@ const ReplacementTracker = require('./replacement-tracker'),
5256
* @returns {String} Normalized string
5357
*/
5458
function normalizeVariables (str, replacements) {
55-
let normalizedString = '',
59+
let normalizedString = E,
5660
pointer = 0, // pointer till witch the string is normalized
5761
variable,
5862
match,
@@ -61,7 +65,7 @@ function normalizeVariables (str, replacements) {
6165
// find all the instances of {{<variable>}} which includes reserved chars
6266
// "Hello {{user#name}}!!!"
6367
// ↑ (pointer = 0)
64-
while ((match = REGEX_EXTRACT_VARS.exec(str)) !== null) {
68+
while ((match = REGEX_ALL_VARIABLES.exec(str)) !== null) {
6569
// {{user#name}}
6670
variable = match[0];
6771

@@ -154,12 +158,14 @@ function parse (urlString) {
154158
},
155159
replacements = new ReplacementTracker(),
156160
pointer = 0,
161+
protocol,
162+
_length,
157163
length,
158164
index,
159165
port;
160166

161167
// bail out if input string is empty
162-
if (!(urlString && typeof urlString === 'string')) {
168+
if (!(urlString && typeof urlString === STRING)) {
163169
return parsedUrl;
164170
}
165171

@@ -191,32 +197,34 @@ function parse (urlString) {
191197
}
192198

193199
// 3. url.protocol
200+
urlString = urlString.replace(REGEX_ALL_BACKSLASHES, PATH_SEPARATOR); // sanitize slashes
201+
202+
// @todo support `protocol:host/path` and `protocol:/host/path`
194203
if ((index = urlString.indexOf(PROTOCOL_SEPARATOR)) !== -1) {
195204
// extract from the front
196-
url.protocol.value = urlString.slice(0, index);
205+
url.protocol.value = protocol = urlString.slice(0, index);
197206
url.protocol.beginIndex = pointer;
198207
url.protocol.endIndex = pointer + index;
199208

200209
urlString = urlString.slice(index + 3);
201210
length -= index + 3;
202211
pointer += index + 3;
203212
}
204-
// protocol can be separated using :\\ as well
205-
else if ((index = urlString.indexOf(PROTOCOL_SEPARATOR_WITH_BACKSLASH)) !== -1) {
206-
// extract from the front
207-
url.protocol.value = urlString.slice(0, index);
208-
url.protocol.beginIndex = pointer;
209-
url.protocol.endIndex = pointer + index;
210213

211-
urlString = urlString.slice(index + 3);
212-
length -= index + 3;
213-
pointer += index + 3;
214-
}
214+
// special handling for leading slashes e.g, http:///example.com
215+
_length = length; // length with leading slashes
215216

216-
// 4. url.path
217-
urlString = urlString.replace(/\\/g, '/'); // sanitize path
218-
urlString = urlString.replace(/^\/+/, ''); // remove leading slashes
217+
urlString = urlString.replace(REGEX_LEADING_SLASHES,
218+
(protocol && protocol.toLowerCase() === FILE_PROTOCOL) ?
219+
// file:////path -> file:///path
220+
PATH_SEPARATOR :
221+
// protocol:////host/path -> protocol://host/path
222+
E);
219223

224+
length = urlString.length; // length without slashes
225+
pointer += _length - length; // update pointer
226+
227+
// 4. url.path
220228
if ((index = urlString.indexOf(PATH_SEPARATOR)) !== -1) {
221229
// extract from the back
222230
url.path.value = urlString.slice(index + 1).split(PATH_SEPARATOR);

test/unit/parser/parser.test.js

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ describe('parser', function () {
220220
});
221221
});
222222

223-
it('should handle handle protocol with backslashes', function () {
223+
it('should handle protocol with backslashes', function () {
224224
expect(parser.parse('http:\\\\localhost')).to.deep.include({
225225
raw: 'http:\\\\localhost',
226226
protocol: 'http',
@@ -229,11 +229,11 @@ describe('parser', function () {
229229
});
230230

231231
it('should handle extra slashes after protocol', function () {
232-
expect(parser.parse('http:////localhost')).to.deep.include({
233-
raw: 'http:////localhost',
232+
expect(parser.parse('http:////localhost/foo')).to.deep.include({
233+
raw: 'http:////localhost/foo',
234234
protocol: 'http',
235235
host: ['localhost'],
236-
path: undefined
236+
path: ['foo']
237237
});
238238
});
239239

@@ -264,6 +264,33 @@ describe('parser', function () {
264264
});
265265
});
266266

267+
it('should handle file://host/foo', function () {
268+
expect(parser.parse('file://host/foo')).to.deep.include({
269+
raw: 'file://host/foo',
270+
protocol: 'file',
271+
host: ['host'],
272+
path: ['foo']
273+
});
274+
});
275+
276+
it('should handle file:///foo/bar', function () {
277+
expect(parser.parse('file:///foo/bar')).to.deep.include({
278+
raw: 'file:///foo/bar',
279+
protocol: 'file',
280+
host: undefined,
281+
path: ['foo', 'bar']
282+
});
283+
});
284+
285+
it('should handle file://///foo/bar', function () {
286+
expect(parser.parse('file://///foo/bar')).to.deep.include({
287+
raw: 'file://///foo/bar',
288+
protocol: 'file',
289+
host: undefined,
290+
path: ['foo', 'bar']
291+
});
292+
});
293+
267294
it('should return default object for empty string input', function () {
268295
expect(parser.parse('')).to.deep.include(defaultObject);
269296
expect(parser.parse(' ')).to.deep.include(defaultObject);

test/unit/toNodeUrl.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,5 +749,29 @@ describe('.toNodeUrl', function () {
749749
href: 'https://example.com/foo/bar'
750750
});
751751
});
752+
753+
// Refer: https://en.wikipedia.org/wiki/File_URI_scheme#How_many_slashes?
754+
it('should handle file://host/path and file:///path', function () {
755+
expect(toNodeUrl('file://host/path')).to.include({
756+
host: 'host',
757+
hostname: 'host',
758+
pathname: '/path',
759+
href: 'file://host/path'
760+
});
761+
762+
expect(toNodeUrl('file:///path')).to.include({
763+
host: '',
764+
hostname: '',
765+
pathname: '/path',
766+
href: 'file:///path'
767+
});
768+
769+
expect(toNodeUrl('file:////foo/bar')).to.include({
770+
host: '',
771+
hostname: '',
772+
pathname: '/foo/bar',
773+
href: 'file:///foo/bar'
774+
});
775+
});
752776
});
753777
});

0 commit comments

Comments
 (0)