• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

unshiftio / url-parse / 3841645403

pending completion
3841645403

push

github

Luigi Pinca
[doc] Fix CI badge URL

203 of 205 branches covered (99.02%)

625 of 625 relevant lines covered (100.0%)

265.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.76
/index.js
1
'use strict';
1✔
2

1✔
3
var required = require('requires-port')
1✔
4
  , qs = require('querystringify')
1✔
5
  , controlOrWhitespace = /^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/
1✔
6
  , CRHTLF = /[\n\r\t]/g
1✔
7
  , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\//
1✔
8
  , port = /:\d+$/
1✔
9
  , protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i
1✔
10
  , windowsDriveLetter = /^[a-zA-Z]:/;
1✔
11

1✔
12
/**
1✔
13
 * Remove control characters and whitespace from the beginning of a string.
1✔
14
 *
1✔
15
 * @param {Object|String} str String to trim.
1✔
16
 * @returns {String} A new string representing `str` stripped of control
1✔
17
 *     characters and whitespace from its beginning.
1✔
18
 * @public
1✔
19
 */
1✔
20
function trimLeft(str) {
364✔
21
  return (str ? str : '').toString().replace(controlOrWhitespace, '');
364✔
22
}
364✔
23

1✔
24
/**
1✔
25
 * Remove control characters and whitespace from both ends of a string.
1✔
26
 *
1✔
27
 * @param {Object|String} str String to trim.
1✔
28
 * @returns {String} A new string representing `str` stripped of control
1✔
29
 *     characters and whitespace from both its beginning and end.
1✔
30
 * @private
1✔
31
 */
1✔
32
function trim(str) {
330✔
33
  var trimmed = trimLeft(str);
330✔
34
  var i = trimmed.length;
330✔
35

330✔
36
  if (i === 0) return trimmed;
330✔
37

324✔
38
  //
324✔
39
  // A regex is not used here because `/[\x00-\x20]+$/` is vulnerable to ReDoS.
324✔
40
  //
324✔
41
  while (i) {
330✔
42
    if (trimmed.charCodeAt(i - 1) > 0x20) {
330✔
43
      break;
324✔
44
    } else {
330✔
45
      i = i - 1;
6✔
46
    }
6✔
47
  }
330✔
48

324✔
49
  return trimmed.slice(0, i);
324✔
50
}
330✔
51

1✔
52
/**
1✔
53
 * These are the parse rules for the URL parser, it informs the parser
1✔
54
 * about:
1✔
55
 *
1✔
56
 * 0. The char it Needs to parse, if it's a string it should be done using
1✔
57
 *    indexOf, RegExp using exec and NaN means set as current value.
1✔
58
 * 1. The property we should set when parsing this value.
1✔
59
 * 2. Indication if it's backwards or forward parsing, when set as number it's
1✔
60
 *    the value of extra chars that should be split off.
1✔
61
 * 3. Inherit from location if non existing in the parser.
1✔
62
 * 4. `toLowerCase` the resulting value.
1✔
63
 */
1✔
64
var rules = [
1✔
65
  ['#', 'hash'],                        // Extract from the back.
1✔
66
  ['?', 'query'],                       // Extract from the back.
1✔
67
  function sanitize(address, url) {     // Sanitize what is left of the address
1✔
68
    return isSpecial(url.protocol) ? address.replace(/\\/g, '/') : address;
323✔
69
  },
1✔
70
  ['/', 'pathname'],                    // Extract from the back.
1✔
71
  ['@', 'auth', 1],                     // Extract from the front.
1✔
72
  [NaN, 'host', undefined, 1, 1],       // Set left over value.
1✔
73
  [/:(\d*)$/, 'port', undefined, 1],    // RegExp the back.
1✔
74
  [NaN, 'hostname', undefined, 1, 1]    // Set left over.
1✔
75
];
1✔
76

1✔
77
/**
1✔
78
 * These properties should not be copied or inherited from. This is only needed
1✔
79
 * for all non blob URL's as a blob URL does not include a hash, only the
1✔
80
 * origin.
1✔
81
 *
1✔
82
 * @type {Object}
1✔
83
 * @private
1✔
84
 */
1✔
85
var ignore = { hash: 1, query: 1 };
1✔
86

1✔
87
/**
1✔
88
 * The location object differs when your code is loaded through a normal page,
1✔
89
 * Worker or through a worker using a blob. And with the blobble begins the
1✔
90
 * trouble as the location object will contain the URL of the blob, not the
1✔
91
 * location of the page where our code is loaded in. The actual origin is
1✔
92
 * encoded in the `pathname` so we can thankfully generate a good "default"
1✔
93
 * location from it so we can generate proper relative URL's again.
1✔
94
 *
1✔
95
 * @param {Object|String} loc Optional default location object.
1✔
96
 * @returns {Object} lolcation object.
1✔
97
 * @public
1✔
98
 */
1✔
99
function lolcation(loc) {
323✔
100
  var globalVar;
323✔
101

323✔
102
  if (typeof window !== 'undefined') globalVar = window;
323!
103
  else if (typeof global !== 'undefined') globalVar = global;
323✔
104
  else if (typeof self !== 'undefined') globalVar = self;
1!
105
  else globalVar = {};
1✔
106

323✔
107
  var location = globalVar.location || {};
323✔
108
  loc = loc || location;
323✔
109

323✔
110
  var finaldestination = {}
323✔
111
    , type = typeof loc
323✔
112
    , key;
323✔
113

323✔
114
  if ('blob:' === loc.protocol) {
323✔
115
    finaldestination = new Url(unescape(loc.pathname), {});
1✔
116
  } else if ('string' === type) {
323✔
117
    finaldestination = new Url(loc, {});
31✔
118
    for (key in ignore) delete finaldestination[key];
31✔
119
  } else if ('object' === type) {
322✔
120
    for (key in loc) {
291✔
121
      if (key in ignore) continue;
157✔
122
      finaldestination[key] = loc[key];
127✔
123
    }
127✔
124

291✔
125
    if (finaldestination.slashes === undefined) {
291✔
126
      finaldestination.slashes = slashes.test(loc.href);
281✔
127
    }
281✔
128
  }
291✔
129

323✔
130
  return finaldestination;
323✔
131
}
323✔
132

1✔
133
/**
1✔
134
 * Check whether a protocol scheme is special.
1✔
135
 *
1✔
136
 * @param {String} The protocol scheme of the URL
1✔
137
 * @return {Boolean} `true` if the protocol scheme is special, else `false`
1✔
138
 * @private
1✔
139
 */
1✔
140
function isSpecial(scheme) {
1,526✔
141
  return (
1,526✔
142
    scheme === 'file:' ||
1,526✔
143
    scheme === 'ftp:' ||
1,526✔
144
    scheme === 'http:' ||
1,526✔
145
    scheme === 'https:' ||
1,526✔
146
    scheme === 'ws:' ||
1,526✔
147
    scheme === 'wss:'
243✔
148
  );
1,526✔
149
}
1,526✔
150

1✔
151
/**
1✔
152
 * @typedef ProtocolExtract
1✔
153
 * @type Object
1✔
154
 * @property {String} protocol Protocol matched in the URL, in lowercase.
1✔
155
 * @property {Boolean} slashes `true` if protocol is followed by "//", else `false`.
1✔
156
 * @property {String} rest Rest of the URL that is not part of the protocol.
1✔
157
 */
1✔
158

1✔
159
/**
1✔
160
 * Extract protocol information from a URL with/without double slash ("//").
1✔
161
 *
1✔
162
 * @param {String} address URL we want to extract from.
1✔
163
 * @param {Object} location
1✔
164
 * @return {ProtocolExtract} Extracted information.
1✔
165
 * @private
1✔
166
 */
1✔
167
function extractProtocol(address, location) {
330✔
168
  address = trim(address);
330✔
169
  address = address.replace(CRHTLF, '');
330✔
170
  location = location || {};
330✔
171

330✔
172
  var match = protocolre.exec(address);
330✔
173
  var protocol = match[1] ? match[1].toLowerCase() : '';
330✔
174
  var forwardSlashes = !!match[2];
330✔
175
  var otherSlashes = !!match[3];
330✔
176
  var slashesCount = 0;
330✔
177
  var rest;
330✔
178

330✔
179
  if (forwardSlashes) {
330✔
180
    if (otherSlashes) {
255✔
181
      rest = match[2] + match[3] + match[4];
11✔
182
      slashesCount = match[2].length + match[3].length;
11✔
183
    } else {
255✔
184
      rest = match[2] + match[4];
244✔
185
      slashesCount = match[2].length;
244✔
186
    }
244✔
187
  } else {
330✔
188
    if (otherSlashes) {
75✔
189
      rest = match[3] + match[4];
28✔
190
      slashesCount = match[3].length;
28✔
191
    } else {
75✔
192
      rest = match[4]
47✔
193
    }
47✔
194
  }
75✔
195

330✔
196
  if (protocol === 'file:') {
330✔
197
    if (slashesCount >= 2) {
13✔
198
      rest = rest.slice(2);
11✔
199
    }
11✔
200
  } else if (isSpecial(protocol)) {
330✔
201
    rest = match[4];
251✔
202
  } else if (protocol) {
317✔
203
    if (forwardSlashes) {
16✔
204
      rest = rest.slice(2);
6✔
205
    }
6✔
206
  } else if (slashesCount >= 2 && isSpecial(location.protocol)) {
66✔
207
    rest = match[4];
3✔
208
  }
3✔
209

330✔
210
  return {
330✔
211
    protocol: protocol,
330✔
212
    slashes: forwardSlashes || isSpecial(protocol),
330✔
213
    slashesCount: slashesCount,
330✔
214
    rest: rest
330✔
215
  };
330✔
216
}
330✔
217

1✔
218
/**
1✔
219
 * Resolve a relative URL pathname against a base URL pathname.
1✔
220
 *
1✔
221
 * @param {String} relative Pathname of the relative URL.
1✔
222
 * @param {String} base Pathname of the base URL.
1✔
223
 * @return {String} Resolved pathname.
1✔
224
 * @private
1✔
225
 */
1✔
226
function resolve(relative, base) {
29✔
227
  if (relative === '') return base;
29✔
228

25✔
229
  var path = (base || '/').split('/').slice(0, -1).concat(relative.split('/'))
29✔
230
    , i = path.length
29✔
231
    , last = path[i - 1]
29✔
232
    , unshift = false
29✔
233
    , up = 0;
29✔
234

29✔
235
  while (i--) {
29✔
236
    if (path[i] === '.') {
96✔
237
      path.splice(i, 1);
12✔
238
    } else if (path[i] === '..') {
96✔
239
      path.splice(i, 1);
17✔
240
      up++;
17✔
241
    } else if (up) {
84✔
242
      if (i === 0) unshift = true;
15✔
243
      path.splice(i, 1);
15✔
244
      up--;
15✔
245
    }
15✔
246
  }
96✔
247

25✔
248
  if (unshift) path.unshift('');
29✔
249
  if (last === '.' || last === '..') path.push('');
29✔
250

25✔
251
  return path.join('/');
25✔
252
}
29✔
253

1✔
254
/**
1✔
255
 * The actual URL instance. Instead of returning an object we've opted-in to
1✔
256
 * create an actual constructor as it's much more memory efficient and
1✔
257
 * faster and it pleases my OCD.
1✔
258
 *
1✔
259
 * It is worth noting that we should not use `URL` as class name to prevent
1✔
260
 * clashes with the global URL instance that got introduced in browsers.
1✔
261
 *
1✔
262
 * @constructor
1✔
263
 * @param {String} address URL we want to parse.
1✔
264
 * @param {Object|String} [location] Location defaults for relative paths.
1✔
265
 * @param {Boolean|Function} [parser] Parser for the query string.
1✔
266
 * @private
1✔
267
 */
1✔
268
function Url(address, location, parser) {
613✔
269
  if (!(this instanceof Url)) {
613✔
270
    return new Url(address, location, parser);
290✔
271
  }
290✔
272

323✔
273
  var relative, extracted, parse, instruction, index, key
323✔
274
    , instructions = rules.slice()
323✔
275
    , type = typeof location
323✔
276
    , url = this
323✔
277
    , i = 0;
323✔
278

323✔
279
  //
323✔
280
  // The following if statements allows this module two have compatibility with
323✔
281
  // 2 different API:
323✔
282
  //
323✔
283
  // 1. Node.js's `url.parse` api which accepts a URL, boolean as arguments
323✔
284
  //    where the boolean indicates that the query string should also be parsed.
323✔
285
  //
323✔
286
  // 2. The `URL` interface of the browser which accepts a URL, object as
323✔
287
  //    arguments. The supplied object will be used as default values / fall-back
323✔
288
  //    for relative paths.
323✔
289
  //
323✔
290
  if ('object' !== type && 'string' !== type) {
613✔
291
    parser = location;
245✔
292
    location = null;
245✔
293
  }
245✔
294

323✔
295
  if (parser && 'function' !== typeof parser) parser = qs.parse;
613✔
296

323✔
297
  location = lolcation(location);
323✔
298

323✔
299
  //
323✔
300
  // Extract protocol information before running the instructions.
323✔
301
  //
323✔
302
  extracted = extractProtocol(address || '', location);
613✔
303
  relative = !extracted.protocol && !extracted.slashes;
613✔
304
  url.slashes = extracted.slashes || relative && location.slashes;
613✔
305
  url.protocol = extracted.protocol || location.protocol || '';
613✔
306
  address = extracted.rest;
613✔
307

613✔
308
  //
613✔
309
  // When the authority component is absent the URL starts with a path
613✔
310
  // component.
613✔
311
  //
613✔
312
  if (
613✔
313
    extracted.protocol === 'file:' && (
613✔
314
      extracted.slashesCount !== 2 || windowsDriveLetter.test(address)) ||
613✔
315
    (!extracted.slashes &&
313✔
316
      (extracted.protocol ||
52✔
317
        extracted.slashesCount < 2 ||
52✔
318
        !isSpecial(url.protocol)))
313✔
319
  ) {
613✔
320
    instructions[3] = [/(.*)/, 'pathname'];
61✔
321
  }
61✔
322

323✔
323
  for (; i < instructions.length; i++) {
613✔
324
    instruction = instructions[i];
2,584✔
325

2,584✔
326
    if (typeof instruction === 'function') {
2,584✔
327
      address = instruction(address, url);
323✔
328
      continue;
323✔
329
    }
323✔
330

2,261✔
331
    parse = instruction[0];
2,261✔
332
    key = instruction[1];
2,261✔
333

2,261✔
334
    if (parse !== parse) {
2,584✔
335
      url[key] = address;
646✔
336
    } else if ('string' === typeof parse) {
2,584✔
337
      index = parse === '@'
1,231✔
338
        ? address.lastIndexOf(parse)
1,231✔
339
        : address.indexOf(parse);
1,231✔
340

1,231✔
341
      if (~index) {
1,231✔
342
        if ('number' === typeof instruction[2]) {
398✔
343
          url[key] = address.slice(0, index);
36✔
344
          address = address.slice(index + instruction[2]);
36✔
345
        } else {
398✔
346
          url[key] = address.slice(index);
362✔
347
          address = address.slice(0, index);
362✔
348
        }
362✔
349
      }
398✔
350
    } else if ((index = parse.exec(address))) {
1,615✔
351
      url[key] = index[1];
95✔
352
      address = address.slice(0, index.index);
95✔
353
    }
95✔
354

2,261✔
355
    url[key] = url[key] || (
2,584✔
356
      relative && instruction[3] ? location[key] || '' : ''
1,313✔
357
    );
2,584✔
358

2,584✔
359
    //
2,584✔
360
    // Hostname, host and protocol should be lowercased so they can be used to
2,584✔
361
    // create a proper `origin`.
2,584✔
362
    //
2,584✔
363
    if (instruction[4]) url[key] = url[key].toLowerCase();
2,584✔
364
  }
2,584✔
365

323✔
366
  //
323✔
367
  // Also parse the supplied query string in to an object. If we're supplied
323✔
368
  // with a custom parser as function use that instead of the default build-in
323✔
369
  // parser.
323✔
370
  //
323✔
371
  if (parser) url.query = parser(url.query);
613✔
372

323✔
373
  //
323✔
374
  // If the URL is relative, resolve the pathname against the base URL.
323✔
375
  //
323✔
376
  if (
323✔
377
      relative
323✔
378
    && location.slashes
613✔
379
    && url.pathname.charAt(0) !== '/'
613✔
380
    && (url.pathname !== '' || location.pathname !== '')
613✔
381
  ) {
613✔
382
    url.pathname = resolve(url.pathname, location.pathname);
29✔
383
  }
29✔
384

323✔
385
  //
323✔
386
  // Default to a / for pathname if none exists. This normalizes the URL
323✔
387
  // to always have a /
323✔
388
  //
323✔
389
  if (url.pathname.charAt(0) !== '/' && isSpecial(url.protocol)) {
613✔
390
    url.pathname = '/' + url.pathname;
42✔
391
  }
42✔
392

323✔
393
  //
323✔
394
  // We should not add port numbers if they are already the default port number
323✔
395
  // for a given protocol. As the host also contains the port number we're going
323✔
396
  // override it with the hostname which contains no port number.
323✔
397
  //
323✔
398
  if (!required(url.port, url.protocol)) {
613✔
399
    url.host = url.hostname;
300✔
400
    url.port = '';
300✔
401
  }
300✔
402

323✔
403
  //
323✔
404
  // Parse down the `auth` for the username and password.
323✔
405
  //
323✔
406
  url.username = url.password = '';
323✔
407

323✔
408
  if (url.auth) {
613✔
409
    index = url.auth.indexOf(':');
26✔
410

26✔
411
    if (~index) {
26✔
412
      url.username = url.auth.slice(0, index);
23✔
413
      url.username = encodeURIComponent(decodeURIComponent(url.username));
23✔
414

23✔
415
      url.password = url.auth.slice(index + 1);
23✔
416
      url.password = encodeURIComponent(decodeURIComponent(url.password))
23✔
417
    } else {
26✔
418
      url.username = encodeURIComponent(decodeURIComponent(url.auth));
3✔
419
    }
3✔
420

26✔
421
    url.auth = url.password ? url.username +':'+ url.password : url.username;
26✔
422
  }
26✔
423

323✔
424
  url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host
613✔
425
    ? url.protocol +'//'+ url.host
613✔
426
    : 'null';
613✔
427

613✔
428
  //
613✔
429
  // The href is just the compiled result.
613✔
430
  //
613✔
431
  url.href = url.toString();
613✔
432
}
613✔
433

1✔
434
/**
1✔
435
 * This is convenience method for changing properties in the URL instance to
1✔
436
 * insure that they all propagate correctly.
1✔
437
 *
1✔
438
 * @param {String} part          Property we need to adjust.
1✔
439
 * @param {Mixed} value          The newly assigned value.
1✔
440
 * @param {Boolean|Function} fn  When setting the query, it will be the function
1✔
441
 *                               used to parse the query.
1✔
442
 *                               When setting the protocol, double slash will be
1✔
443
 *                               removed from the final url if it is truthy.
1✔
444
 *                               When setting the pathname or the hash, a
1✔
445
 *                               leading / or # will not be automatically added
1✔
446
 *                               if it is truthy.
1✔
447
 * @returns {URL} URL instance for chaining.
1✔
448
 * @public
1✔
449
 */
1✔
450
function set(part, value, fn) {
61✔
451
  var url = this;
61✔
452

61✔
453
  switch (part) {
61✔
454
    case 'query':
61✔
455
      if ('string' === typeof value && value.length) {
6✔
456
        value = (fn || qs.parse)(value);
4✔
457
      }
4✔
458

5✔
459
      url[part] = value;
5✔
460
      break;
5✔
461

61✔
462
    case 'port':
61✔
463
      url[part] = value;
7✔
464

7✔
465
      if (!required(value, url.protocol)) {
7✔
466
        url.host = url.hostname;
2✔
467
        url[part] = '';
2✔
468
      } else if (value) {
7✔
469
        url.host = url.hostname +':'+ value;
5✔
470
      }
5✔
471

7✔
472
      break;
7✔
473

61✔
474
    case 'hostname':
61✔
475
      url[part] = value;
5✔
476

5✔
477
      if (url.port) value += ':'+ url.port;
5✔
478
      url.host = value;
5✔
479
      break;
5✔
480

61✔
481
    case 'host':
61✔
482
      url[part] = value;
6✔
483

6✔
484
      if (port.test(value)) {
6✔
485
        value = value.split(':');
3✔
486
        url.port = value.pop();
3✔
487
        url.hostname = value.join(':');
3✔
488
      } else {
3✔
489
        url.hostname = value;
3✔
490
        url.port = '';
3✔
491
      }
3✔
492

6✔
493
      break;
6✔
494

61✔
495
    case 'protocol':
61✔
496
      url.protocol = value.toLowerCase();
9✔
497

9✔
498
      if (
9✔
499
        url.protocol &&
9✔
500
        url.protocol.charAt(url.protocol.length - 1) !== ':'
9✔
501
      ) {
9✔
502
        url.protocol += ':'
4✔
503
      }
4✔
504

9✔
505
      url.slashes = !fn;
9✔
506
      break;
9✔
507

61✔
508
    case 'pathname':
61✔
509
    case 'hash':
61✔
510
      if (value && !fn) {
9✔
511
        var char = part === 'pathname' ? '/' : '#';
5✔
512
        url[part] = value.charAt(0) !== char ? char + value : value;
5✔
513
      } else {
9✔
514
        url[part] = value;
4✔
515
      }
4✔
516
      break;
9✔
517

61✔
518
    case 'username':
61✔
519
    case 'password':
61✔
520
      url[part] = encodeURIComponent(value);
11✔
521
      break;
11✔
522

61✔
523
    case 'auth':
61✔
524
      var index = value.indexOf(':');
8✔
525

8✔
526
      if (~index) {
8✔
527
        url.username = value.slice(0, index);
7✔
528
        url.username = encodeURIComponent(decodeURIComponent(url.username));
7✔
529

7✔
530
        url.password = value.slice(index + 1);
7✔
531
        url.password = encodeURIComponent(decodeURIComponent(url.password));
7✔
532
      } else {
8✔
533
        url.username = encodeURIComponent(decodeURIComponent(value));
1✔
534
      }
1✔
535
  }
61✔
536

60✔
537
  for (var i = 0; i < rules.length; i++) {
61✔
538
    var ins = rules[i];
480✔
539

480✔
540
    if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase();
480✔
541
  }
480✔
542

60✔
543
  url.auth = url.password ? url.username +':'+ url.password : url.username;
61✔
544

61✔
545
  url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host
61✔
546
    ? url.protocol +'//'+ url.host
61✔
547
    : 'null';
61✔
548

61✔
549
  url.href = url.toString();
61✔
550

61✔
551
  return url;
61✔
552
}
61✔
553

1✔
554
/**
1✔
555
 * Transform the properties back in to a valid and full URL string.
1✔
556
 *
1✔
557
 * @param {Function} stringify Optional query stringify function.
1✔
558
 * @returns {String} Compiled version of the URL.
1✔
559
 * @public
1✔
560
 */
1✔
561
function toString(stringify) {
415✔
562
  if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify;
415✔
563

415✔
564
  var query
415✔
565
    , url = this
415✔
566
    , host = url.host
415✔
567
    , protocol = url.protocol;
415✔
568

415✔
569
  if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':';
415✔
570

415✔
571
  var result =
415✔
572
    protocol +
415✔
573
    ((protocol && url.slashes) || isSpecial(protocol) ? '//' : '');
415✔
574

415✔
575
  if (url.username) {
415✔
576
    result += url.username;
36✔
577
    if (url.password) result += ':'+ url.password;
36✔
578
    result += '@';
36✔
579
  } else if (url.password) {
415✔
580
    result += ':'+ url.password;
3✔
581
    result += '@';
3✔
582
  } else if (
379✔
583
    protocol !== 'file:' &&
376✔
584
    isSpecial(protocol) &&
376✔
585
    !host &&
376✔
586
    url.pathname !== '/'
44✔
587
  ) {
376✔
588
    //
19✔
589
    // Add back the empty userinfo, otherwise the original invalid URL
19✔
590
    // might be transformed into a valid one with `url.pathname` as host.
19✔
591
    //
19✔
592
    result += '@';
19✔
593
  }
19✔
594

415✔
595
  //
415✔
596
  // Trailing colon is removed from `url.host` when it is parsed. If it still
415✔
597
  // ends with a colon, then add back the trailing colon that was removed. This
415✔
598
  // prevents an invalid URL from being transformed into a valid one.
415✔
599
  //
415✔
600
  if (host[host.length - 1] === ':' || (port.test(url.hostname) && !url.port)) {
415✔
601
    host += ':';
2✔
602
  }
2✔
603

415✔
604
  result += host + url.pathname;
415✔
605

415✔
606
  query = 'object' === typeof url.query ? stringify(url.query) : url.query;
415✔
607
  if (query) result += '?' !== query.charAt(0) ? '?'+ query : query;
415✔
608

415✔
609
  if (url.hash) result += url.hash;
415✔
610

415✔
611
  return result;
415✔
612
}
415✔
613

1✔
614
Url.prototype = { set: set, toString: toString };
1✔
615

1✔
616
//
1✔
617
// Expose the URL parser and some additional properties that might be useful for
1✔
618
// others or testing.
1✔
619
//
1✔
620
Url.extractProtocol = extractProtocol;
1✔
621
Url.location = lolcation;
1✔
622
Url.trimLeft = trimLeft;
1✔
623
Url.qs = qs;
1✔
624

1✔
625
module.exports = Url;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc