You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

107 lines
3.6 KiB

  1. // Package urls handles converting between various JIRA URL representations in a consistent way. There exists three main
  2. // types of JIRA URL which Go-NEB cares about:
  3. // - URL Keys => matrix.org/jira
  4. // - Base URLs => https://matrix.org/jira/
  5. // - REST URLs => https://matrix.org/jira/rest/api/2/issue/12680
  6. // When making outbound requests to JIRA, Go-NEB needs to use the Base URL representation. Likewise, when Go-NEB
  7. // sends Matrix messages with JIRA URLs in them, the Base URL needs to be used to form the URL. The URL Key is
  8. // used to determine equivalence of various JIRA installations and is mainly required when searching the database.
  9. // The REST URLs are present on incoming webhook events and are the only way to map the event to a JIRA installation.
  10. package urls
  11. import (
  12. "errors"
  13. "net/url"
  14. "strings"
  15. )
  16. // JIRAURL contains the parsed representation of a JIRA URL
  17. type JIRAURL struct {
  18. Base string // The base URL of the JIRA installation. Always has a trailing / and a protocol.
  19. Key string // The URL key of the JIRA installation. Never has a trailing / or a protocol.
  20. Raw string // The raw input URL, if given. Freeform.
  21. }
  22. // ParseJIRAURL parses a raw input URL and returns a struct which has various JIRA URL representations. The input
  23. // URL can be a JIRA REST URL, a speculative base JIRA URL from a client, or a URL key. The input string will be
  24. // stored as under JIRAURL.Raw. If a URL key is given, this struct will default to https as the protocol.
  25. func ParseJIRAURL(u string) (j JIRAURL, err error) {
  26. if u == "" {
  27. err = errors.New("No input JIRA URL")
  28. return
  29. }
  30. j.Raw = u
  31. // URL keys don't have a protocol, everything else does
  32. if !strings.HasPrefix(u, "https://") && !strings.HasPrefix(u, "http://") {
  33. // assume input is a URL key
  34. k, e := makeURLKey(u)
  35. if e != nil {
  36. err = e
  37. return
  38. }
  39. j.Key = k
  40. j.Base = makeBaseURL(u)
  41. return
  42. }
  43. // Attempt to parse out REST API paths. This is a horrible heuristic which mostly works.
  44. if strings.Contains(u, "/rest/api/") {
  45. j.Base = makeBaseURL(strings.Split(u, "/rest/api/")[0])
  46. } else {
  47. // Assume it already is a base URL
  48. j.Base = makeBaseURL(u)
  49. }
  50. k, e := makeURLKey(j.Base)
  51. if e != nil {
  52. err = e
  53. return
  54. }
  55. j.Key = k
  56. return
  57. }
  58. // SameJIRAURL returns true if the two given JIRA URLs are pointing to the same JIRA installation.
  59. // Equivalence is determined solely by the provided URLs, by sanitising them then comparing.
  60. func SameJIRAURL(a, b string) bool {
  61. ja, err := ParseJIRAURL(a)
  62. if err != nil {
  63. return false
  64. }
  65. jb, err := ParseJIRAURL(b)
  66. if err != nil {
  67. return false
  68. }
  69. return ja.Key == jb.Key
  70. }
  71. // makeBaseURL assumes the input is a base URL and makes sure that the string conforms to JIRA Base URL rules:
  72. // - Must have a protocol
  73. // - Must have a trailing slash
  74. // Defaults to HTTPS if there is no protocol specified.
  75. func makeBaseURL(s string) string {
  76. if !strings.HasPrefix(s, "https://") && !strings.HasPrefix(s, "http://") {
  77. s = "https://" + s
  78. }
  79. return withTrailingSlash(s)
  80. }
  81. // makeURLKey assumes the input is a URL key and makes sure that the string conforms to JIRA URL Key rules:
  82. // - Must not have a protocol
  83. // - Must not have a trailing slash
  84. // For example:
  85. // https://matrix.org/jira/ => matrix.org/jira
  86. func makeURLKey(s string) (string, error) {
  87. u, err := url.Parse(s)
  88. if err != nil {
  89. return "", err
  90. }
  91. return u.Host + strings.TrimSuffix(u.Path, "/"), nil
  92. }
  93. // withTrailingSlash makes sure the input string has a trailing slash. Will not add one if one already exists.
  94. func withTrailingSlash(s string) string {
  95. if strings.HasSuffix(s, "/") {
  96. return s
  97. }
  98. return s + "/"
  99. }