http_test.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. //go:build integration
  7. // +build integration
  8. package integration
  9. import (
  10. "bytes"
  11. "io"
  12. "net/http"
  13. "os"
  14. "strings"
  15. "testing"
  16. "github.com/syncthing/syncthing/lib/protocol"
  17. "github.com/syncthing/syncthing/lib/rc"
  18. )
  19. func TestHTTPGetIndex(t *testing.T) {
  20. p := startInstance(t, 2)
  21. defer checkedStop(t, p)
  22. // Check for explicit index.html
  23. res, err := http.Get("http://localhost:8082/index.html")
  24. if err != nil {
  25. t.Fatal(err)
  26. }
  27. if res.StatusCode != 200 {
  28. t.Errorf("Status %d != 200", res.StatusCode)
  29. }
  30. bs, err := io.ReadAll(res.Body)
  31. if err != nil {
  32. t.Fatal(err)
  33. }
  34. if len(bs) < 1024 {
  35. t.Errorf("Length %d < 1024", len(bs))
  36. }
  37. if !bytes.Contains(bs, []byte("</html>")) {
  38. t.Error("Incorrect response")
  39. }
  40. if res.Header.Get("Set-Cookie") == "" {
  41. t.Error("No set-cookie header")
  42. }
  43. res.Body.Close()
  44. // Check for implicit index.html
  45. res, err = http.Get("http://localhost:8082/")
  46. if err != nil {
  47. t.Fatal(err)
  48. }
  49. if res.StatusCode != 200 {
  50. t.Errorf("Status %d != 200", res.StatusCode)
  51. }
  52. bs, err = io.ReadAll(res.Body)
  53. if err != nil {
  54. t.Fatal(err)
  55. }
  56. if len(bs) < 1024 {
  57. t.Errorf("Length %d < 1024", len(bs))
  58. }
  59. if !bytes.Contains(bs, []byte("</html>")) {
  60. t.Error("Incorrect response")
  61. }
  62. if res.Header.Get("Set-Cookie") == "" {
  63. t.Error("No set-cookie header")
  64. }
  65. res.Body.Close()
  66. }
  67. func TestHTTPGetIndexAuth(t *testing.T) {
  68. p := startInstance(t, 1)
  69. defer checkedStop(t, p)
  70. // Without auth should give 401
  71. res, err := http.Get("http://127.0.0.1:8081/")
  72. if err != nil {
  73. t.Fatal(err)
  74. }
  75. res.Body.Close()
  76. if res.StatusCode != 401 {
  77. t.Errorf("Status %d != 401", res.StatusCode)
  78. }
  79. // With wrong username/password should give 401
  80. req, err := http.NewRequest("GET", "http://127.0.0.1:8081/", nil)
  81. if err != nil {
  82. t.Fatal(err)
  83. }
  84. req.SetBasicAuth("testuser", "wrongpass")
  85. res, err = http.DefaultClient.Do(req)
  86. if err != nil {
  87. t.Fatal(err)
  88. }
  89. res.Body.Close()
  90. if res.StatusCode != 401 {
  91. t.Fatalf("Status %d != 401", res.StatusCode)
  92. }
  93. // With correct username/password should succeed
  94. req, err = http.NewRequest("GET", "http://127.0.0.1:8081/", nil)
  95. if err != nil {
  96. t.Fatal(err)
  97. }
  98. req.SetBasicAuth("testuser", "testpass")
  99. res, err = http.DefaultClient.Do(req)
  100. if err != nil {
  101. t.Fatal(err)
  102. }
  103. res.Body.Close()
  104. if res.StatusCode != 200 {
  105. t.Fatalf("Status %d != 200", res.StatusCode)
  106. }
  107. }
  108. func TestHTTPOptions(t *testing.T) {
  109. p := startInstance(t, 2)
  110. defer checkedStop(t, p)
  111. req, err := http.NewRequest("OPTIONS", "http://127.0.0.1:8082/rest/system/error/clear", nil)
  112. if err != nil {
  113. t.Fatal(err)
  114. }
  115. res, err := http.DefaultClient.Do(req)
  116. if err != nil {
  117. t.Fatal(err)
  118. }
  119. res.Body.Close()
  120. if res.StatusCode != 204 {
  121. t.Fatalf("Status %d != 204 for OPTIONS", res.StatusCode)
  122. }
  123. }
  124. func TestHTTPPOSTWithoutCSRF(t *testing.T) {
  125. p := startInstance(t, 2)
  126. defer checkedStop(t, p)
  127. // Should fail without CSRF
  128. req, err := http.NewRequest("POST", "http://127.0.0.1:8082/rest/system/error/clear", nil)
  129. if err != nil {
  130. t.Fatal(err)
  131. }
  132. res, err := http.DefaultClient.Do(req)
  133. if err != nil {
  134. t.Fatal(err)
  135. }
  136. res.Body.Close()
  137. if res.StatusCode != 403 {
  138. t.Fatalf("Status %d != 403 for POST", res.StatusCode)
  139. }
  140. // Get CSRF
  141. req, err = http.NewRequest("GET", "http://127.0.0.1:8082/", nil)
  142. if err != nil {
  143. t.Fatal(err)
  144. }
  145. res, err = http.DefaultClient.Do(req)
  146. if err != nil {
  147. t.Fatal(err)
  148. }
  149. res.Body.Close()
  150. hdr := res.Header.Get("Set-Cookie")
  151. id := res.Header.Get("X-Syncthing-ID")[:protocol.ShortIDStringLength]
  152. if !strings.Contains(hdr, "CSRF-Token") {
  153. t.Error("Missing CSRF-Token in", hdr)
  154. }
  155. // Should succeed with CSRF
  156. req, err = http.NewRequest("POST", "http://127.0.0.1:8082/rest/system/error/clear", nil)
  157. if err != nil {
  158. t.Fatal(err)
  159. }
  160. req.Header.Set("X-CSRF-Token-"+id, hdr[len("CSRF-Token-"+id+"="):])
  161. res, err = http.DefaultClient.Do(req)
  162. if err != nil {
  163. t.Fatal(err)
  164. }
  165. res.Body.Close()
  166. if res.StatusCode != 200 {
  167. t.Fatalf("Status %d != 200 for POST", res.StatusCode)
  168. }
  169. // Should fail with incorrect CSRF
  170. req, err = http.NewRequest("POST", "http://127.0.0.1:8082/rest/system/error/clear", nil)
  171. if err != nil {
  172. t.Fatal(err)
  173. }
  174. req.Header.Set("X-CSRF-Token-"+id, hdr[len("CSRF-Token-"+id+"="):]+"X")
  175. res, err = http.DefaultClient.Do(req)
  176. if err != nil {
  177. t.Fatal(err)
  178. }
  179. res.Body.Close()
  180. if res.StatusCode != 403 {
  181. t.Fatalf("Status %d != 403 for POST", res.StatusCode)
  182. }
  183. }
  184. func setupAPIBench() *rc.Process {
  185. err := removeAll("s1", "s2", "h1/index*", "h2/index*")
  186. if err != nil {
  187. panic(err)
  188. }
  189. err = generateFiles("s1", 25000, 20, "../LICENSE")
  190. if err != nil {
  191. panic(err)
  192. }
  193. err = os.WriteFile("s1/knownfile", []byte("somedatahere"), 0644)
  194. if err != nil {
  195. panic(err)
  196. }
  197. // This will panic if there is an actual failure to start, when we try to
  198. // call nil.Fatal(...)
  199. return startInstance(nil, 1)
  200. }
  201. func benchmarkURL(b *testing.B, url string) {
  202. p := setupAPIBench()
  203. defer p.Stop()
  204. b.ResetTimer()
  205. for i := 0; i < b.N; i++ {
  206. _, err := p.Get(url)
  207. if err != nil {
  208. b.Fatal(err)
  209. }
  210. }
  211. }
  212. func BenchmarkAPI_db_completion(b *testing.B) {
  213. benchmarkURL(b, "/rest/db/completion?folder=default&device="+protocol.LocalDeviceID.String())
  214. }
  215. func BenchmarkAPI_db_file(b *testing.B) {
  216. benchmarkURL(b, "/rest/db/file?folder=default&file=knownfile")
  217. }
  218. func BenchmarkAPI_db_ignores(b *testing.B) {
  219. benchmarkURL(b, "/rest/db/ignores?folder=default")
  220. }
  221. func BenchmarkAPI_db_need(b *testing.B) {
  222. benchmarkURL(b, "/rest/db/need?folder=default")
  223. }
  224. func BenchmarkAPI_db_status(b *testing.B) {
  225. benchmarkURL(b, "/rest/db/status?folder=default")
  226. }
  227. func BenchmarkAPI_db_browse_dirsonly(b *testing.B) {
  228. benchmarkURL(b, "/rest/db/browse?folder=default&dirsonly=true")
  229. }