parse-url.js 2.5 KB

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