1const url = require('url') 2 3const lastIndexOfBefore = (str, char, beforeChar) => { 4 const startPosition = str.indexOf(beforeChar) 5 return str.lastIndexOf(char, startPosition > -1 ? startPosition : Infinity) 6} 7 8const safeUrl = (u) => { 9 try { 10 return new url.URL(u) 11 } catch { 12 // this fn should never throw 13 } 14} 15 16// accepts input like git:github.com:user/repo and inserts the // after the first : 17const correctProtocol = (arg, protocols) => { 18 const firstColon = arg.indexOf(':') 19 const proto = arg.slice(0, firstColon + 1) 20 if (Object.prototype.hasOwnProperty.call(protocols, proto)) { 21 return arg 22 } 23 24 const firstAt = arg.indexOf('@') 25 if (firstAt > -1) { 26 if (firstAt > firstColon) { 27 return `git+ssh://${arg}` 28 } else { 29 return arg 30 } 31 } 32 33 const doubleSlash = arg.indexOf('//') 34 if (doubleSlash === firstColon + 1) { 35 return arg 36 } 37 38 return `${arg.slice(0, firstColon + 1)}//${arg.slice(firstColon + 1)}` 39} 40 41// attempt to correct an scp style url so that it will parse with `new URL()` 42const correctUrl = (giturl) => { 43 // ignore @ that come after the first hash since the denotes the start 44 // of a committish which can contain @ characters 45 const firstAt = lastIndexOfBefore(giturl, '@', '#') 46 // ignore colons that come after the hash since that could include colons such as: 47 // git@github.com:user/package-2#semver:^1.0.0 48 const lastColonBeforeHash = lastIndexOfBefore(giturl, ':', '#') 49 50 if (lastColonBeforeHash > firstAt) { 51 // the last : comes after the first @ (or there is no @) 52 // like it would in: 53 // proto://hostname.com:user/repo 54 // username@hostname.com:user/repo 55 // :password@hostname.com:user/repo 56 // username:password@hostname.com:user/repo 57 // proto://username@hostname.com:user/repo 58 // proto://:password@hostname.com:user/repo 59 // proto://username:password@hostname.com:user/repo 60 // then we replace the last : with a / to create a valid path 61 giturl = giturl.slice(0, lastColonBeforeHash) + '/' + giturl.slice(lastColonBeforeHash + 1) 62 } 63 64 if (lastIndexOfBefore(giturl, ':', '#') === -1 && giturl.indexOf('//') === -1) { 65 // we have no : at all 66 // as it would be in: 67 // username@hostname.com/user/repo 68 // then we prepend a protocol 69 giturl = `git+ssh://${giturl}` 70 } 71 72 return giturl 73} 74 75module.exports = (giturl, protocols) => { 76 const withProtocol = protocols ? correctProtocol(giturl, protocols) : giturl 77 return safeUrl(withProtocol) || safeUrl(correctUrl(withProtocol)) 78} 79