Skip to content

Commit 74b3c7e

Browse files
authored
fix: use URL instead of url.parse (#141)
1 parent ea00495 commit 74b3c7e

File tree

1 file changed

+46
-49
lines changed

1 file changed

+46
-49
lines changed

lib/npa.js

+46-49
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module.exports.resolve = resolve
44
module.exports.toPurl = toPurl
55
module.exports.Result = Result
66

7-
const url = require('url')
7+
const { URL } = require('url')
88
const HostedGit = require('hosted-git-info')
99
const semver = require('semver')
1010
const path = global.FAKE_WINDOWS ? require('path').win32 : require('path')
@@ -183,10 +183,11 @@ Result.prototype.toJSON = function () {
183183
return result
184184
}
185185

186-
function setGitCommittish (res, committish) {
186+
// sets res.gitCommittish, res.gitRange, and res.gitSubdir
187+
function setGitAttrs (res, committish) {
187188
if (!committish) {
188189
res.gitCommittish = null
189-
return res
190+
return
190191
}
191192

192193
// for each :: separated item:
@@ -224,8 +225,6 @@ function setGitCommittish (res, committish) {
224225
}
225226
log.warn('npm-package-arg', `ignoring unknown key "${name}"`)
226227
}
227-
228-
return res
229228
}
230229

231230
function fromFile (res, where) {
@@ -245,8 +244,8 @@ function fromFile (res, where) {
245244
const rawWithPrefix = prefix + res.rawSpec
246245
let rawNoPrefix = rawWithPrefix.replace(/^file:/, '')
247246
try {
248-
resolvedUrl = new url.URL(rawWithPrefix, `file://${path.resolve(where)}/`)
249-
specUrl = new url.URL(rawWithPrefix)
247+
resolvedUrl = new URL(rawWithPrefix, `file://${path.resolve(where)}/`)
248+
specUrl = new URL(rawWithPrefix)
250249
} catch (originalError) {
251250
const er = new Error('Invalid file: URL, must comply with RFC 8089')
252251
throw Object.assign(er, {
@@ -260,17 +259,17 @@ function fromFile (res, where) {
260259
// XXX backwards compatibility lack of compliance with RFC 8089
261260
if (resolvedUrl.host && resolvedUrl.host !== 'localhost') {
262261
const rawSpec = res.rawSpec.replace(/^file:\/\//, 'file:///')
263-
resolvedUrl = new url.URL(rawSpec, `file://${path.resolve(where)}/`)
264-
specUrl = new url.URL(rawSpec)
262+
resolvedUrl = new URL(rawSpec, `file://${path.resolve(where)}/`)
263+
specUrl = new URL(rawSpec)
265264
rawNoPrefix = rawSpec.replace(/^file:/, '')
266265
}
267266
// turn file:/../foo into file:../foo
268267
// for 1, 2 or 3 leading slashes since we attempted
269268
// in the previous step to make it a file protocol url with a leading slash
270269
if (/^\/{1,3}\.\.?(\/|$)/.test(rawNoPrefix)) {
271270
const rawSpec = res.rawSpec.replace(/^file:\/{1,3}/, 'file:')
272-
resolvedUrl = new url.URL(rawSpec, `file://${path.resolve(where)}/`)
273-
specUrl = new url.URL(rawSpec)
271+
resolvedUrl = new URL(rawSpec, `file://${path.resolve(where)}/`)
272+
specUrl = new URL(rawSpec)
274273
rawNoPrefix = rawSpec.replace(/^file:/, '')
275274
}
276275
// XXX end RFC 8089 violation backwards compatibility section
@@ -303,7 +302,8 @@ function fromHostedGit (res, hosted) {
303302
res.hosted = hosted
304303
res.saveSpec = hosted.toString({ noGitPlus: false, noCommittish: false })
305304
res.fetchSpec = hosted.getDefaultRepresentation() === 'shortcut' ? null : hosted.toString()
306-
return setGitCommittish(res, hosted.committish)
305+
setGitAttrs(res, hosted.committish)
306+
return res
307307
}
308308

309309
function unsupportedURLType (protocol, spec) {
@@ -312,62 +312,59 @@ function unsupportedURLType (protocol, spec) {
312312
return err
313313
}
314314

315-
function matchGitScp (spec) {
316-
// git ssh specifiers are overloaded to also use scp-style git
317-
// specifiers, so we have to parse those out and treat them special.
318-
// They are NOT true URIs, so we can't hand them to `url.parse`.
319-
//
320-
// This regex looks for things that look like:
321-
// git+ssh://[email protected]:username/project.git#deadbeef
322-
//
323-
// ...and various combinations. The username in the beginning is *required*.
324-
const matched = spec.match(/^git\+ssh:\/\/([^:#]+:[^#]+(?:\.git)?)(?:#(.*))?$/i)
325-
return matched && !matched[1].match(/:[0-9]+\/?.*$/i) && {
326-
fetchSpec: matched[1],
327-
gitCommittish: matched[2] == null ? null : matched[2],
328-
}
329-
}
330-
331315
function fromURL (res) {
332-
// eslint-disable-next-line node/no-deprecated-api
333-
const urlparse = url.parse(res.rawSpec)
334-
res.saveSpec = res.rawSpec
316+
let rawSpec = res.rawSpec
317+
res.saveSpec = rawSpec
318+
if (rawSpec.startsWith('git+ssh:')) {
319+
// git ssh specifiers are overloaded to also use scp-style git
320+
// specifiers, so we have to parse those out and treat them special.
321+
// They are NOT true URIs, so we can't hand them to URL.
322+
323+
// This regex looks for things that look like:
324+
// git+ssh://[email protected]:username/project.git#deadbeef
325+
// ...and various combinations. The username in the beginning is *required*.
326+
const matched = rawSpec.match(/^git\+ssh:\/\/([^:#]+:[^#]+(?:\.git)?)(?:#(.*))?$/i)
327+
if (matched && !matched[1].match(/:[0-9]+\/?.*$/i)) {
328+
res.type = 'git'
329+
setGitAttrs(res, matched[2])
330+
res.fetchSpec = matched[1]
331+
return res
332+
}
333+
} else if (rawSpec.startsWith('git+file://')) {
334+
// URL can't handle windows paths
335+
rawSpec = rawSpec.replace(/\\/g, '/')
336+
}
337+
const parsedUrl = new URL(rawSpec)
335338
// check the protocol, and then see if it's git or not
336-
switch (urlparse.protocol) {
339+
switch (parsedUrl.protocol) {
337340
case 'git:':
338341
case 'git+http:':
339342
case 'git+https:':
340343
case 'git+rsync:':
341344
case 'git+ftp:':
342345
case 'git+file:':
343-
case 'git+ssh:': {
346+
case 'git+ssh:':
344347
res.type = 'git'
345-
const match = urlparse.protocol === 'git+ssh:' ? matchGitScp(res.rawSpec)
346-
: null
347-
if (match) {
348-
setGitCommittish(res, match.gitCommittish)
349-
res.fetchSpec = match.fetchSpec
348+
setGitAttrs(res, parsedUrl.hash.slice(1))
349+
if (parsedUrl.protocol === 'git+file:' && /^git\+file:\/\/[a-z]:/i.test(rawSpec)) {
350+
// URL can't handle drive letters on windows file paths, the host can't contain a :
351+
res.fetchSpec = `git+file://${parsedUrl.host.toLowerCase()}:${parsedUrl.pathname}`
350352
} else {
351-
setGitCommittish(res, urlparse.hash != null ? urlparse.hash.slice(1) : '')
352-
urlparse.protocol = urlparse.protocol.replace(/^git[+]/, '')
353-
if (urlparse.protocol === 'file:' && /^git\+file:\/\/[a-z]:/i.test(res.rawSpec)) {
354-
// keep the drive letter : on windows file paths
355-
urlparse.host += ':'
356-
urlparse.hostname += ':'
357-
}
358-
delete urlparse.hash
359-
res.fetchSpec = url.format(urlparse)
353+
parsedUrl.hash = ''
354+
res.fetchSpec = parsedUrl.toString()
355+
}
356+
if (res.fetchSpec.startsWith('git+')) {
357+
res.fetchSpec = res.fetchSpec.slice(4)
360358
}
361359
break
362-
}
363360
case 'http:':
364361
case 'https:':
365362
res.type = 'remote'
366363
res.fetchSpec = res.saveSpec
367364
break
368365

369366
default:
370-
throw unsupportedURLType(urlparse.protocol, res.rawSpec)
367+
throw unsupportedURLType(parsedUrl.protocol, rawSpec)
371368
}
372369

373370
return res

0 commit comments

Comments
 (0)