-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: use hosted-git-info to parse registry urls
Previously this was using `new URL` which would fail on some urls that `hosted-git-info` is able to parse. But if we still get a url that can't be parsed, we now set it to be removed from the tree instead of erroring. Fixes: #5278
- Loading branch information
1 parent
292156c
commit 9f467d2
Showing
9 changed files
with
229 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,25 @@ | ||
'use strict' | ||
const url = require('url') | ||
const gitHosts = require('./git-host-info.js') | ||
const GitHost = module.exports = require('./git-host.js') | ||
const LRU = require('lru-cache') | ||
const cache = new LRU({ max: 1000 }) | ||
|
||
const protocolToRepresentationMap = { | ||
'git+ssh:': 'sshurl', | ||
'git+https:': 'https', | ||
'ssh:': 'sshurl', | ||
'git:': 'git', | ||
} | ||
const parseUrl = require('./parse-url.js') | ||
|
||
function protocolToRepresentation (protocol) { | ||
return protocolToRepresentationMap[protocol] || protocol.slice(0, -1) | ||
} | ||
const cache = new LRU({ max: 1000 }) | ||
|
||
const authProtocols = { | ||
'git:': true, | ||
'https:': true, | ||
'git+https:': true, | ||
'http:': true, | ||
'git+http:': true, | ||
const protocols = { | ||
'git+ssh:': { name: 'sshurl' }, | ||
'ssh:': { name: 'sshurl' }, | ||
'git+https:': { name: 'https', auth: true }, | ||
'git:': { auth: true }, | ||
'http:': { auth: true }, | ||
'https:': { auth: true }, | ||
'git+http:': { auth: true }, | ||
...Object.keys(gitHosts.byShortcut).reduce((acc, key) => { | ||
acc[key] = { name: gitHosts.byShortcut[key] } | ||
return acc | ||
}, {}), | ||
} | ||
|
||
const knownProtocols = Object.keys(gitHosts.byShortcut) | ||
.concat(['http:', 'https:', 'git:', 'git+ssh:', 'git+https:', 'ssh:']) | ||
|
||
module.exports.fromUrl = function (giturl, opts) { | ||
if (typeof giturl !== 'string') { | ||
return | ||
|
@@ -41,30 +34,34 @@ module.exports.fromUrl = function (giturl, opts) { | |
return cache.get(key) | ||
} | ||
|
||
module.exports.parseUrl = parseUrl | ||
|
||
function fromUrl (giturl, opts) { | ||
if (!giturl) { | ||
return | ||
} | ||
|
||
const correctedUrl = isGitHubShorthand(giturl) ? 'github:' + giturl : correctProtocol(giturl) | ||
const parsed = parseGitUrl(correctedUrl) | ||
const correctedUrl = isGitHubShorthand(giturl) ? `github:${giturl}` : giturl | ||
const parsed = parseUrl(correctedUrl, protocols) | ||
if (!parsed) { | ||
return parsed | ||
return | ||
} | ||
|
||
const gitHostShortcut = gitHosts.byShortcut[parsed.protocol] | ||
const gitHostDomain = | ||
gitHosts.byDomain[parsed.hostname.startsWith('www.') ? | ||
parsed.hostname.slice(4) : | ||
parsed.hostname] | ||
const gitHostDomain = gitHosts.byDomain[parsed.hostname.startsWith('www.') | ||
? parsed.hostname.slice(4) | ||
: parsed.hostname] | ||
const gitHostName = gitHostShortcut || gitHostDomain | ||
if (!gitHostName) { | ||
return | ||
} | ||
|
||
const gitHostInfo = gitHosts[gitHostShortcut || gitHostDomain] | ||
let auth = null | ||
if (authProtocols[parsed.protocol] && (parsed.username || parsed.password)) { | ||
if (protocols[parsed.protocol] && | ||
protocols[parsed.protocol].auth && | ||
(parsed.username || parsed.password) | ||
) { | ||
auth = `${parsed.username}${parsed.password ? ':' + parsed.password : ''}` | ||
} | ||
|
||
|
@@ -116,7 +113,8 @@ function fromUrl (giturl, opts) { | |
user = segments.user && decodeURIComponent(segments.user) | ||
project = decodeURIComponent(segments.project) | ||
committish = decodeURIComponent(segments.committish) | ||
defaultRepresentation = protocolToRepresentation(parsed.protocol) | ||
defaultRepresentation = (protocols[parsed.protocol] && protocols[parsed.protocol].name) | ||
|| parsed.protocol.slice(0, -1) | ||
} | ||
} catch (err) { | ||
/* istanbul ignore else */ | ||
|
@@ -130,31 +128,6 @@ function fromUrl (giturl, opts) { | |
return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) | ||
} | ||
|
||
// accepts input like git:github.com:user/repo and inserts the // after the first : | ||
const correctProtocol = (arg) => { | ||
const firstColon = arg.indexOf(':') | ||
const proto = arg.slice(0, firstColon + 1) | ||
if (knownProtocols.includes(proto)) { | ||
return arg | ||
} | ||
|
||
const firstAt = arg.indexOf('@') | ||
if (firstAt > -1) { | ||
if (firstAt > firstColon) { | ||
return `git+ssh://${arg}` | ||
} else { | ||
return arg | ||
} | ||
} | ||
|
||
const doubleSlash = arg.indexOf('//') | ||
if (doubleSlash === firstColon + 1) { | ||
return arg | ||
} | ||
|
||
return arg.slice(0, firstColon + 1) + '//' + arg.slice(firstColon + 1) | ||
} | ||
|
||
// look for github shorthand inputs, such as npm/cli | ||
const isGitHubShorthand = (arg) => { | ||
// it cannot contain whitespace before the first # | ||
|
@@ -185,64 +158,3 @@ const isGitHubShorthand = (arg) => { | |
doesNotStartWithDot && atOnlyAfterHash && colonOnlyAfterHash && | ||
secondSlashOnlyAfterHash | ||
} | ||
|
||
// attempt to correct an scp style url so that it will parse with `new URL()` | ||
const correctUrl = (giturl) => { | ||
const firstAt = giturl.indexOf('@') | ||
const lastHash = giturl.lastIndexOf('#') | ||
let firstColon = giturl.indexOf(':') | ||
let lastColon = giturl.lastIndexOf(':', lastHash > -1 ? lastHash : Infinity) | ||
|
||
let corrected | ||
if (lastColon > firstAt) { | ||
// the last : comes after the first @ (or there is no @) | ||
// like it would in: | ||
// proto://hostname.com:user/repo | ||
// [email protected]:user/repo | ||
// :[email protected]:user/repo | ||
// username:[email protected]:user/repo | ||
// proto://[email protected]:user/repo | ||
// proto://:[email protected]:user/repo | ||
// proto://username:[email protected]:user/repo | ||
// then we replace the last : with a / to create a valid path | ||
corrected = giturl.slice(0, lastColon) + '/' + giturl.slice(lastColon + 1) | ||
// // and we find our new : positions | ||
firstColon = corrected.indexOf(':') | ||
lastColon = corrected.lastIndexOf(':') | ||
} | ||
|
||
if (firstColon === -1 && giturl.indexOf('//') === -1) { | ||
// we have no : at all | ||
// as it would be in: | ||
// [email protected]/user/repo | ||
// then we prepend a protocol | ||
corrected = `git+ssh://${corrected}` | ||
} | ||
|
||
return corrected | ||
} | ||
|
||
// try to parse the url as its given to us, if that throws | ||
// then we try to clean the url and parse that result instead | ||
// THIS FUNCTION SHOULD NEVER THROW | ||
const parseGitUrl = (giturl) => { | ||
let result | ||
try { | ||
result = new url.URL(giturl) | ||
} catch { | ||
// this fn should never throw | ||
} | ||
|
||
if (result) { | ||
return result | ||
} | ||
|
||
const correctedUrl = correctUrl(giturl) | ||
try { | ||
result = new url.URL(correctedUrl) | ||
} catch { | ||
// this fn should never throw | ||
} | ||
|
||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
const url = require('url') | ||
|
||
const lastIndexOfBefore = (str, char, beforeChar) => { | ||
const startPosition = str.indexOf(beforeChar) | ||
return str.lastIndexOf(char, startPosition > -1 ? startPosition : Infinity) | ||
} | ||
|
||
const safeUrl = (u) => { | ||
try { | ||
return new url.URL(u) | ||
} catch { | ||
// this fn should never throw | ||
} | ||
} | ||
|
||
// accepts input like git:github.com:user/repo and inserts the // after the first : | ||
const correctProtocol = (arg, protocols) => { | ||
const firstColon = arg.indexOf(':') | ||
const proto = arg.slice(0, firstColon + 1) | ||
if (Object.prototype.hasOwnProperty.call(protocols, proto)) { | ||
return arg | ||
} | ||
|
||
const firstAt = arg.indexOf('@') | ||
if (firstAt > -1) { | ||
if (firstAt > firstColon) { | ||
return `git+ssh://${arg}` | ||
} else { | ||
return arg | ||
} | ||
} | ||
|
||
const doubleSlash = arg.indexOf('//') | ||
if (doubleSlash === firstColon + 1) { | ||
return arg | ||
} | ||
|
||
return `${arg.slice(0, firstColon + 1)}//${arg.slice(firstColon + 1)}` | ||
} | ||
|
||
// attempt to correct an scp style url so that it will parse with `new URL()` | ||
const correctUrl = (giturl) => { | ||
// ignore @ that come after the first hash since the denotes the start | ||
// of a committish which can contain @ characters | ||
const firstAt = lastIndexOfBefore(giturl, '@', '#') | ||
// ignore colons that come after the hash since that could include colons such as: | ||
// [email protected]:user/package-2#semver:^1.0.0 | ||
const lastColonBeforeHash = lastIndexOfBefore(giturl, ':', '#') | ||
|
||
if (lastColonBeforeHash > firstAt) { | ||
// the last : comes after the first @ (or there is no @) | ||
// like it would in: | ||
// proto://hostname.com:user/repo | ||
// [email protected]:user/repo | ||
// :[email protected]:user/repo | ||
// username:[email protected]:user/repo | ||
// proto://[email protected]:user/repo | ||
// proto://:[email protected]:user/repo | ||
// proto://username:[email protected]:user/repo | ||
// then we replace the last : with a / to create a valid path | ||
giturl = giturl.slice(0, lastColonBeforeHash) + '/' + giturl.slice(lastColonBeforeHash + 1) | ||
} | ||
|
||
if (lastIndexOfBefore(giturl, ':', '#') === -1 && giturl.indexOf('//') === -1) { | ||
// we have no : at all | ||
// as it would be in: | ||
// [email protected]/user/repo | ||
// then we prepend a protocol | ||
giturl = `git+ssh://${giturl}` | ||
} | ||
|
||
return giturl | ||
} | ||
|
||
module.exports = (giturl, protocols) => { | ||
const withProtocol = protocols ? correctProtocol(giturl, protocols) : giturl | ||
return safeUrl(withProtocol) || safeUrl(correctUrl(withProtocol)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.