stcrashreceiver.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. // Copyright (C) 2019 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. package main
  7. import (
  8. "io"
  9. "log"
  10. "net/http"
  11. "path"
  12. "strings"
  13. "sync"
  14. )
  15. type crashReceiver struct {
  16. store *diskStore
  17. sentry *sentryService
  18. ignore *ignorePatterns
  19. ignoredMut sync.RWMutex
  20. ignored map[string]struct{}
  21. }
  22. func (r *crashReceiver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  23. // The final path component should be a SHA256 hash in hex, so 64 hex
  24. // characters. We don't care about case on the request but use lower
  25. // case internally.
  26. reportID := strings.ToLower(path.Base(req.URL.Path))
  27. if len(reportID) != 64 {
  28. http.Error(w, "Bad request", http.StatusBadRequest)
  29. return
  30. }
  31. for _, c := range reportID {
  32. if c >= 'a' && c <= 'f' {
  33. continue
  34. }
  35. if c >= '0' && c <= '9' {
  36. continue
  37. }
  38. http.Error(w, "Bad request", http.StatusBadRequest)
  39. return
  40. }
  41. switch req.Method {
  42. case http.MethodGet:
  43. r.serveGet(reportID, w, req)
  44. case http.MethodHead:
  45. r.serveHead(reportID, w, req)
  46. case http.MethodPut:
  47. r.servePut(reportID, w, req)
  48. default:
  49. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  50. }
  51. }
  52. // serveGet responds to GET requests by serving the uncompressed report.
  53. func (r *crashReceiver) serveGet(reportID string, w http.ResponseWriter, _ *http.Request) {
  54. bs, err := r.store.Get(reportID)
  55. if err != nil {
  56. http.Error(w, "Not found", http.StatusNotFound)
  57. return
  58. }
  59. w.Write(bs)
  60. }
  61. // serveHead responds to HEAD requests by checking if the named report
  62. // already exists in the system.
  63. func (r *crashReceiver) serveHead(reportID string, w http.ResponseWriter, _ *http.Request) {
  64. r.ignoredMut.RLock()
  65. _, ignored := r.ignored[reportID]
  66. r.ignoredMut.RUnlock()
  67. if ignored {
  68. return // found
  69. }
  70. if !r.store.Exists(reportID) {
  71. http.Error(w, "Not found", http.StatusNotFound)
  72. }
  73. }
  74. // servePut accepts and stores the given report.
  75. func (r *crashReceiver) servePut(reportID string, w http.ResponseWriter, req *http.Request) {
  76. result := "receive_failure"
  77. defer func() {
  78. metricCrashReportsTotal.WithLabelValues(result).Inc()
  79. }()
  80. r.ignoredMut.RLock()
  81. _, ignored := r.ignored[reportID]
  82. r.ignoredMut.RUnlock()
  83. if ignored {
  84. result = "ignored_cached"
  85. io.Copy(io.Discard, req.Body)
  86. return // found
  87. }
  88. // Read at most maxRequestSize of report data.
  89. log.Println("Receiving report", reportID)
  90. lr := io.LimitReader(req.Body, maxRequestSize)
  91. bs, err := io.ReadAll(lr)
  92. if err != nil {
  93. log.Println("Reading report:", err)
  94. http.Error(w, "Internal server error", http.StatusInternalServerError)
  95. return
  96. }
  97. if r.ignore.match(bs) {
  98. r.ignoredMut.Lock()
  99. if r.ignored == nil {
  100. r.ignored = make(map[string]struct{})
  101. }
  102. r.ignored[reportID] = struct{}{}
  103. r.ignoredMut.Unlock()
  104. result = "ignored"
  105. return
  106. }
  107. result = "success"
  108. // Store the report
  109. if !r.store.Put(reportID, bs) {
  110. log.Println("Failed to store report (queue full):", reportID)
  111. result = "queue_failure"
  112. }
  113. // Send the report to Sentry
  114. if !r.sentry.Send(reportID, userIDFor(req), bs) {
  115. log.Println("Failed to send report to sentry (queue full):", reportID)
  116. result = "sentry_failure"
  117. }
  118. }