1'use strict' 2var url = require('url') 3var gitHosts = require('./git-host-info.js') 4var GitHost = module.exports = require('./git-host.js') 5 6var protocolToRepresentationMap = { 7 'git+ssh:': 'sshurl', 8 'git+https:': 'https', 9 'ssh:': 'sshurl', 10 'git:': 'git' 11} 12 13function protocolToRepresentation (protocol) { 14 return protocolToRepresentationMap[protocol] || protocol.slice(0, -1) 15} 16 17var authProtocols = { 18 'git:': true, 19 'https:': true, 20 'git+https:': true, 21 'http:': true, 22 'git+http:': true 23} 24 25var cache = {} 26 27module.exports.fromUrl = function (giturl, opts) { 28 if (typeof giturl !== 'string') return 29 var key = giturl + JSON.stringify(opts || {}) 30 31 if (!(key in cache)) { 32 cache[key] = fromUrl(giturl, opts) 33 } 34 35 return cache[key] 36} 37 38function fromUrl (giturl, opts) { 39 if (giturl == null || giturl === '') return 40 var url = fixupUnqualifiedGist( 41 isGitHubShorthand(giturl) ? 'github:' + giturl : giturl 42 ) 43 var parsed = parseGitUrl(url) 44 var shortcutMatch = url.match(/^([^:]+):(?:[^@]+@)?(?:([^/]*)\/)?([^#]+)/) 45 var matches = Object.keys(gitHosts).map(function (gitHostName) { 46 try { 47 var gitHostInfo = gitHosts[gitHostName] 48 var auth = null 49 if (parsed.auth && authProtocols[parsed.protocol]) { 50 auth = parsed.auth 51 } 52 var committish = parsed.hash ? decodeURIComponent(parsed.hash.substr(1)) : null 53 var user = null 54 var project = null 55 var defaultRepresentation = null 56 if (shortcutMatch && shortcutMatch[1] === gitHostName) { 57 user = shortcutMatch[2] && decodeURIComponent(shortcutMatch[2]) 58 project = decodeURIComponent(shortcutMatch[3].replace(/\.git$/, '')) 59 defaultRepresentation = 'shortcut' 60 } else { 61 if (parsed.host && parsed.host !== gitHostInfo.domain && parsed.host.replace(/^www[.]/, '') !== gitHostInfo.domain) return 62 if (!gitHostInfo.protocols_re.test(parsed.protocol)) return 63 if (!parsed.path) return 64 var pathmatch = gitHostInfo.pathmatch 65 var matched = parsed.path.match(pathmatch) 66 if (!matched) return 67 /* istanbul ignore else */ 68 if (matched[1] !== null && matched[1] !== undefined) { 69 user = decodeURIComponent(matched[1].replace(/^:/, '')) 70 } 71 project = decodeURIComponent(matched[2]) 72 defaultRepresentation = protocolToRepresentation(parsed.protocol) 73 } 74 return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) 75 } catch (ex) { 76 /* istanbul ignore else */ 77 if (ex instanceof URIError) { 78 } else throw ex 79 } 80 }).filter(function (gitHostInfo) { return gitHostInfo }) 81 if (matches.length !== 1) return 82 return matches[0] 83} 84 85function isGitHubShorthand (arg) { 86 // Note: This does not fully test the git ref format. 87 // See https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html 88 // 89 // The only way to do this properly would be to shell out to 90 // git-check-ref-format, and as this is a fast sync function, 91 // we don't want to do that. Just let git fail if it turns 92 // out that the commit-ish is invalid. 93 // GH usernames cannot start with . or - 94 return /^[^:@%/\s.-][^:@%/\s]*[/][^:@\s/%]+(?:#.*)?$/.test(arg) 95} 96 97function fixupUnqualifiedGist (giturl) { 98 // necessary for round-tripping gists 99 var parsed = url.parse(giturl) 100 if (parsed.protocol === 'gist:' && parsed.host && !parsed.path) { 101 return parsed.protocol + '/' + parsed.host 102 } else { 103 return giturl 104 } 105} 106 107function parseGitUrl (giturl) { 108 var matched = giturl.match(/^([^@]+)@([^:/]+):[/]?((?:[^/]+[/])?[^/]+?)(?:[.]git)?(#.*)?$/) 109 if (!matched) { 110 var legacy = url.parse(giturl) 111 // If we don't have url.URL, then sorry, this is just not fixable. 112 // This affects Node <= 6.12. 113 if (legacy.auth && typeof url.URL === 'function') { 114 // git urls can be in the form of scp-style/ssh-connect strings, like 115 // git+ssh://user@host.com:some/path, which the legacy url parser 116 // supports, but WhatWG url.URL class does not. However, the legacy 117 // parser de-urlencodes the username and password, so something like 118 // https://user%3An%40me:p%40ss%3Aword@x.com/ becomes 119 // https://user:n@me:p@ss:word@x.com/ which is all kinds of wrong. 120 // Pull off just the auth and host, so we dont' get the confusing 121 // scp-style URL, then pass that to the WhatWG parser to get the 122 // auth properly escaped. 123 var authmatch = giturl.match(/[^@]+@[^:/]+/) 124 /* istanbul ignore else - this should be impossible */ 125 if (authmatch) { 126 var whatwg = new url.URL(authmatch[0]) 127 legacy.auth = whatwg.username || '' 128 if (whatwg.password) legacy.auth += ':' + whatwg.password 129 } 130 } 131 return legacy 132 } 133 return { 134 protocol: 'git+ssh:', 135 slashes: true, 136 auth: matched[1], 137 host: matched[2], 138 port: null, 139 hostname: matched[2], 140 hash: matched[4], 141 search: null, 142 query: null, 143 pathname: '/' + matched[3], 144 path: '/' + matched[3], 145 href: 'git+ssh://' + matched[1] + '@' + matched[2] + 146 '/' + matched[3] + (matched[4] || '') 147 } 148} 149