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