url_test.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package urllib // import "miniflux.app/v2/internal/urllib"
  4. import (
  5. "net"
  6. "testing"
  7. )
  8. func TestIsRelativePath(t *testing.T) {
  9. scenarios := map[string]bool{
  10. // Valid relative paths
  11. "path/to/file.ext": true,
  12. "./path/to/file.ext": true,
  13. "../path/to/file.ext": true,
  14. "file.ext": true,
  15. "./file.ext": true,
  16. "../file.ext": true,
  17. "/absolute/path": true,
  18. "path?query=value": true,
  19. "path#fragment": true,
  20. "path?query#fragment": true,
  21. // Not relative paths
  22. "https://example.org/file.ext": false,
  23. "http://example.org/file.ext": false,
  24. "//example.org/file.ext": false,
  25. "//example.org": false,
  26. "ftp://example.org/file.ext": false,
  27. "mailto:user@example.org": false,
  28. "magnet:?xt=urn:btih:example": false,
  29. "": false,
  30. "magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C": false,
  31. }
  32. for input, expected := range scenarios {
  33. actual := IsRelativePath(input)
  34. if actual != expected {
  35. t.Errorf(`Unexpected result for IsRelativePath, got %v instead of %v for %q`, actual, expected, input)
  36. }
  37. }
  38. }
  39. func TestIsAbsoluteURL(t *testing.T) {
  40. scenarios := map[string]bool{
  41. "https://example.org/file.pdf": true,
  42. "magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7": true,
  43. "invalid url": false,
  44. }
  45. for input, expected := range scenarios {
  46. actual := IsAbsoluteURL(input)
  47. if actual != expected {
  48. t.Errorf(`Unexpected result, got %v instead of %v for %q`, actual, expected, input)
  49. }
  50. }
  51. }
  52. func TestAbsoluteURL(t *testing.T) {
  53. scenarios := [][]string{
  54. {"https://example.org/path/file.ext", "https://example.org/folder/", "/path/file.ext"},
  55. {"https://example.org/folder/path/file.ext", "https://example.org/folder/", "path/file.ext"},
  56. {"https://example.org/", "https://example.org/path", "./"},
  57. {"https://example.org/folder/", "https://example.org/folder/", "./"},
  58. {"https://example.org/path/file.ext", "https://example.org/folder", "path/file.ext"},
  59. {"https://example.org/path/file.ext", "https://example.org/folder/", "https://example.org/path/file.ext"},
  60. {"https://static.example.org/path/file.ext", "https://www.example.org/", "//static.example.org/path/file.ext"},
  61. {"magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a", "https://www.example.org/", "magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"},
  62. {"magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7", "https://www.example.org/", "magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7"},
  63. }
  64. for _, scenario := range scenarios {
  65. actual, err := AbsoluteURL(scenario[1], scenario[2])
  66. if err != nil {
  67. t.Errorf(`Got error for (%q, %q): %v`, scenario[1], scenario[2], err)
  68. }
  69. if actual != scenario[0] {
  70. t.Errorf(`Unexpected result, got %q instead of %q for (%q, %q)`, actual, scenario[0], scenario[1], scenario[2])
  71. }
  72. }
  73. }
  74. func TestRootURL(t *testing.T) {
  75. scenarios := map[string]string{
  76. "https://example.org/path/file.ext": "https://example.org/",
  77. "//static.example.org/path/file.ext": "https://static.example.org/",
  78. "https://example|org/path/file.ext": "https://example|org/path/file.ext",
  79. }
  80. for input, expected := range scenarios {
  81. actual := RootURL(input)
  82. if actual != expected {
  83. t.Errorf(`Unexpected result, got %q instead of %q`, actual, expected)
  84. }
  85. }
  86. }
  87. func TestIsHTTPS(t *testing.T) {
  88. scenarios := map[string]bool{
  89. "https://example.org/": true,
  90. "http://example.org/": false,
  91. "https://example|org/": false,
  92. }
  93. for input, expected := range scenarios {
  94. actual := IsHTTPS(input)
  95. if actual != expected {
  96. t.Errorf(`Unexpected result, got %v instead of %v`, actual, expected)
  97. }
  98. }
  99. }
  100. func TestDomain(t *testing.T) {
  101. scenarios := map[string]string{
  102. "https://static.example.org/": "static.example.org",
  103. "https://example|org/": "https://example|org/",
  104. }
  105. for input, expected := range scenarios {
  106. actual := Domain(input)
  107. if actual != expected {
  108. t.Errorf(`Unexpected result, got %q instead of %q`, actual, expected)
  109. }
  110. }
  111. }
  112. func TestJoinBaseURLAndPath(t *testing.T) {
  113. type args struct {
  114. baseURL string
  115. path string
  116. }
  117. tests := []struct {
  118. name string
  119. args args
  120. want string
  121. wantErr bool
  122. }{
  123. {"empty base url", args{"", "/api/bookmarks/"}, "", true},
  124. {"empty path", args{"https://example.com", ""}, "", true},
  125. {"invalid base url", args{"incorrect url", ""}, "", true},
  126. {"valid", args{"https://example.com", "/api/bookmarks/"}, "https://example.com/api/bookmarks/", false},
  127. {"valid", args{"https://example.com/subfolder", "/api/bookmarks/"}, "https://example.com/subfolder/api/bookmarks/", false},
  128. }
  129. for _, tt := range tests {
  130. t.Run(tt.name, func(t *testing.T) {
  131. got, err := JoinBaseURLAndPath(tt.args.baseURL, tt.args.path)
  132. if (err != nil) != tt.wantErr {
  133. t.Errorf("JoinBaseURLAndPath error = %v, wantErr %v", err, tt.wantErr)
  134. return
  135. }
  136. if got != tt.want {
  137. t.Errorf("JoinBaseURLAndPath = %v, want %v", got, tt.want)
  138. }
  139. })
  140. }
  141. }
  142. func TestIsNonPublicIP(t *testing.T) {
  143. testCases := []struct {
  144. name string
  145. ipString string
  146. want bool
  147. }{
  148. {"nil", "", true},
  149. {"private IPv4", "192.168.1.10", true},
  150. {"loopback IPv4", "127.0.0.1", true},
  151. {"link-local IPv4", "169.254.42.1", true},
  152. {"multicast IPv4", "224.0.0.1", true},
  153. {"unspecified IPv6", "::", true},
  154. {"loopback IPv6", "::1", true},
  155. {"multicast IPv6", "ff02::1", true},
  156. {"public IPv4", "93.184.216.34", false},
  157. {"public IPv6", "2001:4860:4860::8888", false},
  158. }
  159. for _, tc := range testCases {
  160. t.Run(tc.name, func(t *testing.T) {
  161. var ip net.IP
  162. if tc.ipString != "" {
  163. ip = net.ParseIP(tc.ipString)
  164. if ip == nil {
  165. t.Fatalf("unable to parse %q", tc.ipString)
  166. }
  167. }
  168. if got := isNonPublicIP(ip); got != tc.want {
  169. t.Fatalf("unexpected result for %s: got %v want %v", tc.name, got, tc.want)
  170. }
  171. })
  172. }
  173. }
  174. func TestResolvesToPrivateIP(t *testing.T) {
  175. testCases := []struct {
  176. name string
  177. host string
  178. want bool
  179. }{
  180. {"localhost", "localhost", true},
  181. {"example.org", "example.org", false},
  182. {"loopback IPv4 literal", "127.0.0.1", true},
  183. {"loopback IPv6 literal", "::1", true},
  184. {"private IPv4 literal", "192.168.1.1", true},
  185. {"public IPv4 literal", "93.184.216.34", false},
  186. {"public IPv6 literal", "2001:4860:4860::8888", false},
  187. }
  188. for _, tc := range testCases {
  189. t.Run(tc.name, func(t *testing.T) {
  190. got, err := ResolvesToPrivateIP(tc.host)
  191. if err != nil {
  192. t.Fatalf("unexpected error for %s: %v", tc.host, err)
  193. }
  194. if got != tc.want {
  195. t.Fatalf("unexpected result for %s: got %v want %v", tc.name, got, tc.want)
  196. }
  197. })
  198. }
  199. }
  200. func TestResolvesToPrivateIPError(t *testing.T) {
  201. if _, err := ResolvesToPrivateIP(""); err == nil {
  202. t.Fatalf("expected an error for empty host")
  203. }
  204. }