gui.go 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712
  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. package main
  7. import (
  8. "bytes"
  9. "crypto/tls"
  10. "encoding/json"
  11. "fmt"
  12. "io"
  13. "io/ioutil"
  14. "net"
  15. "net/http"
  16. "net/url"
  17. "os"
  18. "path/filepath"
  19. "reflect"
  20. "regexp"
  21. "runtime"
  22. "runtime/pprof"
  23. "sort"
  24. "strconv"
  25. "strings"
  26. "time"
  27. "github.com/rcrowley/go-metrics"
  28. "github.com/syncthing/syncthing/lib/config"
  29. "github.com/syncthing/syncthing/lib/connections"
  30. "github.com/syncthing/syncthing/lib/db"
  31. "github.com/syncthing/syncthing/lib/discover"
  32. "github.com/syncthing/syncthing/lib/events"
  33. "github.com/syncthing/syncthing/lib/fs"
  34. "github.com/syncthing/syncthing/lib/logger"
  35. "github.com/syncthing/syncthing/lib/model"
  36. "github.com/syncthing/syncthing/lib/protocol"
  37. "github.com/syncthing/syncthing/lib/rand"
  38. "github.com/syncthing/syncthing/lib/stats"
  39. "github.com/syncthing/syncthing/lib/sync"
  40. "github.com/syncthing/syncthing/lib/tlsutil"
  41. "github.com/syncthing/syncthing/lib/upgrade"
  42. "github.com/syncthing/syncthing/lib/versioner"
  43. "github.com/vitrun/qart/qr"
  44. "golang.org/x/crypto/bcrypt"
  45. )
  46. var (
  47. startTime = time.Now()
  48. // matches a bcrypt hash and not too much else
  49. bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`)
  50. )
  51. const (
  52. defaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
  53. diskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
  54. eventSubBufferSize = 1000
  55. )
  56. type apiService struct {
  57. id protocol.DeviceID
  58. cfg configIntf
  59. httpsCertFile string
  60. httpsKeyFile string
  61. statics *staticsServer
  62. model modelIntf
  63. eventSubs map[events.EventType]events.BufferedSubscription
  64. eventSubsMut sync.Mutex
  65. discoverer discover.CachingMux
  66. connectionsService connectionsIntf
  67. fss *folderSummaryService
  68. systemConfigMut sync.Mutex // serializes posts to /rest/system/config
  69. stop chan struct{} // signals intentional stop
  70. configChanged chan struct{} // signals intentional listener close due to config change
  71. started chan string // signals startup complete by sending the listener address, for testing only
  72. startedOnce chan struct{} // the service has started successfully at least once
  73. cpu rater
  74. guiErrors logger.Recorder
  75. systemLog logger.Recorder
  76. }
  77. type modelIntf interface {
  78. GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
  79. Completion(device protocol.DeviceID, folder string) model.FolderCompletion
  80. Override(folder string)
  81. Revert(folder string)
  82. NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated)
  83. RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error)
  84. NeedSize(folder string) db.Counts
  85. ConnectionStats() map[string]interface{}
  86. DeviceStatistics() map[string]stats.DeviceStatistics
  87. FolderStatistics() map[string]stats.FolderStatistics
  88. CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool)
  89. CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool)
  90. ResetFolder(folder string)
  91. Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []model.Availability
  92. GetIgnores(folder string) ([]string, []string, error)
  93. GetFolderVersions(folder string) (map[string][]versioner.FileVersion, error)
  94. RestoreFolderVersions(folder string, versions map[string]time.Time) (map[string]string, error)
  95. SetIgnores(folder string, content []string) error
  96. DelayScan(folder string, next time.Duration)
  97. ScanFolder(folder string) error
  98. ScanFolders() map[string]error
  99. ScanFolderSubdirs(folder string, subs []string) error
  100. BringToFront(folder, file string)
  101. Connection(deviceID protocol.DeviceID) (connections.Connection, bool)
  102. GlobalSize(folder string) db.Counts
  103. LocalSize(folder string) db.Counts
  104. ReceiveOnlyChangedSize(folder string) db.Counts
  105. CurrentSequence(folder string) (int64, bool)
  106. RemoteSequence(folder string) (int64, bool)
  107. State(folder string) (string, time.Time, error)
  108. UsageReportingStats(version int, preview bool) map[string]interface{}
  109. PullErrors(folder string) ([]model.FileError, error)
  110. WatchError(folder string) error
  111. }
  112. type configIntf interface {
  113. GUI() config.GUIConfiguration
  114. LDAP() config.LDAPConfiguration
  115. RawCopy() config.Configuration
  116. Options() config.OptionsConfiguration
  117. Replace(cfg config.Configuration) (config.Waiter, error)
  118. Subscribe(c config.Committer)
  119. Folders() map[string]config.FolderConfiguration
  120. Devices() map[protocol.DeviceID]config.DeviceConfiguration
  121. SetDevice(config.DeviceConfiguration) (config.Waiter, error)
  122. SetDevices([]config.DeviceConfiguration) (config.Waiter, error)
  123. Save() error
  124. ListenAddresses() []string
  125. RequiresRestart() bool
  126. }
  127. type connectionsIntf interface {
  128. Status() map[string]interface{}
  129. NATType() string
  130. }
  131. type rater interface {
  132. Rate() float64
  133. }
  134. func newAPIService(id protocol.DeviceID, cfg configIntf, httpsCertFile, httpsKeyFile, assetDir string, m modelIntf, defaultSub, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connectionsIntf, errors, systemLog logger.Recorder, cpu rater) *apiService {
  135. service := &apiService{
  136. id: id,
  137. cfg: cfg,
  138. httpsCertFile: httpsCertFile,
  139. httpsKeyFile: httpsKeyFile,
  140. statics: newStaticsServer(cfg.GUI().Theme, assetDir),
  141. model: m,
  142. eventSubs: map[events.EventType]events.BufferedSubscription{
  143. defaultEventMask: defaultSub,
  144. diskEventMask: diskSub,
  145. },
  146. eventSubsMut: sync.NewMutex(),
  147. discoverer: discoverer,
  148. connectionsService: connectionsService,
  149. systemConfigMut: sync.NewMutex(),
  150. stop: make(chan struct{}),
  151. configChanged: make(chan struct{}),
  152. startedOnce: make(chan struct{}),
  153. guiErrors: errors,
  154. systemLog: systemLog,
  155. cpu: cpu,
  156. }
  157. return service
  158. }
  159. func (s *apiService) getListener(guiCfg config.GUIConfiguration) (net.Listener, error) {
  160. cert, err := tls.LoadX509KeyPair(s.httpsCertFile, s.httpsKeyFile)
  161. if err != nil {
  162. l.Infoln("Loading HTTPS certificate:", err)
  163. l.Infoln("Creating new HTTPS certificate")
  164. // When generating the HTTPS certificate, use the system host name per
  165. // default. If that isn't available, use the "syncthing" default.
  166. var name string
  167. name, err = os.Hostname()
  168. if err != nil {
  169. name = tlsDefaultCommonName
  170. }
  171. cert, err = tlsutil.NewCertificate(s.httpsCertFile, s.httpsKeyFile, name)
  172. }
  173. if err != nil {
  174. return nil, err
  175. }
  176. tlsCfg := tlsutil.SecureDefault()
  177. tlsCfg.Certificates = []tls.Certificate{cert}
  178. if guiCfg.Network() == "unix" {
  179. // When listening on a UNIX socket we should unlink before bind,
  180. // lest we get a "bind: address already in use". We don't
  181. // particularly care if this succeeds or not.
  182. os.Remove(guiCfg.Address())
  183. }
  184. rawListener, err := net.Listen(guiCfg.Network(), guiCfg.Address())
  185. if err != nil {
  186. return nil, err
  187. }
  188. listener := &tlsutil.DowngradingListener{
  189. Listener: rawListener,
  190. TLSConfig: tlsCfg,
  191. }
  192. return listener, nil
  193. }
  194. func sendJSON(w http.ResponseWriter, jsonObject interface{}) {
  195. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  196. // Marshalling might fail, in which case we should return a 500 with the
  197. // actual error.
  198. bs, err := json.MarshalIndent(jsonObject, "", " ")
  199. if err != nil {
  200. // This Marshal() can't fail though.
  201. bs, _ = json.Marshal(map[string]string{"error": err.Error()})
  202. http.Error(w, string(bs), http.StatusInternalServerError)
  203. return
  204. }
  205. fmt.Fprintf(w, "%s\n", bs)
  206. }
  207. func (s *apiService) Serve() {
  208. listener, err := s.getListener(s.cfg.GUI())
  209. if err != nil {
  210. select {
  211. case <-s.startedOnce:
  212. // We let this be a loud user-visible warning as it may be the only
  213. // indication they get that the GUI won't be available.
  214. l.Warnln("Starting API/GUI:", err)
  215. return
  216. default:
  217. // This is during initialization. A failure here should be fatal
  218. // as there will be no way for the user to communicate with us
  219. // otherwise anyway.
  220. l.Fatalln("Starting API/GUI:", err)
  221. }
  222. }
  223. if listener == nil {
  224. // Not much we can do here other than exit quickly. The supervisor
  225. // will log an error at some point.
  226. return
  227. }
  228. defer listener.Close()
  229. // The GET handlers
  230. getRestMux := http.NewServeMux()
  231. getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // device folder
  232. getRestMux.HandleFunc("/rest/db/file", s.getDBFile) // folder file
  233. getRestMux.HandleFunc("/rest/db/ignores", s.getDBIgnores) // folder
  234. getRestMux.HandleFunc("/rest/db/need", s.getDBNeed) // folder [perpage] [page]
  235. getRestMux.HandleFunc("/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page]
  236. getRestMux.HandleFunc("/rest/db/status", s.getDBStatus) // folder
  237. getRestMux.HandleFunc("/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
  238. getRestMux.HandleFunc("/rest/folder/versions", s.getFolderVersions) // folder
  239. getRestMux.HandleFunc("/rest/folder/pullerrors", s.getPullErrors) // folder
  240. getRestMux.HandleFunc("/rest/events", s.getIndexEvents) // [since] [limit] [timeout] [events]
  241. getRestMux.HandleFunc("/rest/events/disk", s.getDiskEvents) // [since] [limit] [timeout]
  242. getRestMux.HandleFunc("/rest/stats/device", s.getDeviceStats) // -
  243. getRestMux.HandleFunc("/rest/stats/folder", s.getFolderStats) // -
  244. getRestMux.HandleFunc("/rest/svc/deviceid", s.getDeviceID) // id
  245. getRestMux.HandleFunc("/rest/svc/lang", s.getLang) // -
  246. getRestMux.HandleFunc("/rest/svc/report", s.getReport) // -
  247. getRestMux.HandleFunc("/rest/svc/random/string", s.getRandomString) // [length]
  248. getRestMux.HandleFunc("/rest/system/browse", s.getSystemBrowse) // current
  249. getRestMux.HandleFunc("/rest/system/config", s.getSystemConfig) // -
  250. getRestMux.HandleFunc("/rest/system/config/insync", s.getSystemConfigInsync) // -
  251. getRestMux.HandleFunc("/rest/system/connections", s.getSystemConnections) // -
  252. getRestMux.HandleFunc("/rest/system/discovery", s.getSystemDiscovery) // -
  253. getRestMux.HandleFunc("/rest/system/error", s.getSystemError) // -
  254. getRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
  255. getRestMux.HandleFunc("/rest/system/status", s.getSystemStatus) // -
  256. getRestMux.HandleFunc("/rest/system/upgrade", s.getSystemUpgrade) // -
  257. getRestMux.HandleFunc("/rest/system/version", s.getSystemVersion) // -
  258. getRestMux.HandleFunc("/rest/system/debug", s.getSystemDebug) // -
  259. getRestMux.HandleFunc("/rest/system/log", s.getSystemLog) // [since]
  260. getRestMux.HandleFunc("/rest/system/log.txt", s.getSystemLogTxt) // [since]
  261. // The POST handlers
  262. postRestMux := http.NewServeMux()
  263. postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
  264. postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder
  265. postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
  266. postRestMux.HandleFunc("/rest/db/revert", s.postDBRevert) // folder
  267. postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
  268. postRestMux.HandleFunc("/rest/folder/versions", s.postFolderVersionsRestore) // folder <body>
  269. postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
  270. postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // <body>
  271. postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
  272. postRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
  273. postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset) // [folder]
  274. postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart) // -
  275. postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown) // -
  276. postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
  277. postRestMux.HandleFunc("/rest/system/pause", s.makeDevicePauseHandler(true)) // [device]
  278. postRestMux.HandleFunc("/rest/system/resume", s.makeDevicePauseHandler(false)) // [device]
  279. postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
  280. // Debug endpoints, not for general use
  281. debugMux := http.NewServeMux()
  282. debugMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
  283. debugMux.HandleFunc("/rest/debug/httpmetrics", s.getSystemHTTPMetrics)
  284. debugMux.HandleFunc("/rest/debug/cpuprof", s.getCPUProf) // duration
  285. debugMux.HandleFunc("/rest/debug/heapprof", s.getHeapProf)
  286. debugMux.HandleFunc("/rest/debug/support", s.getSupportBundle)
  287. getRestMux.Handle("/rest/debug/", s.whenDebugging(debugMux))
  288. // A handler that splits requests between the two above and disables
  289. // caching
  290. restMux := noCacheMiddleware(metricsMiddleware(getPostHandler(getRestMux, postRestMux)))
  291. // The main routing handler
  292. mux := http.NewServeMux()
  293. mux.Handle("/rest/", restMux)
  294. mux.HandleFunc("/qr/", s.getQR)
  295. // Serve compiled in assets unless an asset directory was set (for development)
  296. mux.Handle("/", s.statics)
  297. // Handle the special meta.js path
  298. mux.HandleFunc("/meta.js", s.getJSMetadata)
  299. guiCfg := s.cfg.GUI()
  300. // Wrap everything in CSRF protection. The /rest prefix should be
  301. // protected, other requests will grant cookies.
  302. handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg, mux)
  303. // Add our version and ID as a header to responses
  304. handler = withDetailsMiddleware(s.id, handler)
  305. // Wrap everything in basic auth, if user/password is set.
  306. if guiCfg.IsAuthEnabled() {
  307. handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, s.cfg.LDAP(), handler)
  308. }
  309. // Redirect to HTTPS if we are supposed to
  310. if guiCfg.UseTLS() {
  311. handler = redirectToHTTPSMiddleware(handler)
  312. }
  313. // Add the CORS handling
  314. handler = corsMiddleware(handler, guiCfg.InsecureAllowFrameLoading)
  315. if addressIsLocalhost(guiCfg.Address()) && !guiCfg.InsecureSkipHostCheck {
  316. // Verify source host
  317. handler = localhostMiddleware(handler)
  318. }
  319. handler = debugMiddleware(handler)
  320. srv := http.Server{
  321. Handler: handler,
  322. // ReadTimeout must be longer than SyncthingController $scope.refresh
  323. // interval to avoid HTTP keepalive/GUI refresh race.
  324. ReadTimeout: 15 * time.Second,
  325. }
  326. s.fss = newFolderSummaryService(s.cfg, s.model)
  327. defer s.fss.Stop()
  328. s.fss.ServeBackground()
  329. l.Infoln("GUI and API listening on", listener.Addr())
  330. l.Infoln("Access the GUI via the following URL:", guiCfg.URL())
  331. if s.started != nil {
  332. // only set when run by the tests
  333. s.started <- listener.Addr().String()
  334. }
  335. // Indicate successful initial startup, to ourselves and to interested
  336. // listeners (i.e. the thing that starts the browser).
  337. select {
  338. case <-s.startedOnce:
  339. default:
  340. close(s.startedOnce)
  341. }
  342. // Serve in the background
  343. serveError := make(chan error, 1)
  344. go func() {
  345. serveError <- srv.Serve(listener)
  346. }()
  347. // Wait for stop, restart or error signals
  348. select {
  349. case <-s.stop:
  350. // Shutting down permanently
  351. l.Debugln("shutting down (stop)")
  352. case <-s.configChanged:
  353. // Soft restart due to configuration change
  354. l.Debugln("restarting (config changed)")
  355. case <-serveError:
  356. // Restart due to listen/serve failure
  357. l.Warnln("GUI/API:", err, "(restarting)")
  358. }
  359. }
  360. func (s *apiService) Stop() {
  361. close(s.stop)
  362. }
  363. func (s *apiService) String() string {
  364. return fmt.Sprintf("apiService@%p", s)
  365. }
  366. func (s *apiService) VerifyConfiguration(from, to config.Configuration) error {
  367. if to.GUI.Network() != "tcp" {
  368. return nil
  369. }
  370. _, err := net.ResolveTCPAddr("tcp", to.GUI.Address())
  371. return err
  372. }
  373. func (s *apiService) CommitConfiguration(from, to config.Configuration) bool {
  374. // No action required when this changes, so mask the fact that it changed at all.
  375. from.GUI.Debugging = to.GUI.Debugging
  376. if to.GUI == from.GUI {
  377. return true
  378. }
  379. if to.GUI.Theme != from.GUI.Theme {
  380. s.statics.setTheme(to.GUI.Theme)
  381. }
  382. // Tell the serve loop to restart
  383. s.configChanged <- struct{}{}
  384. return true
  385. }
  386. func getPostHandler(get, post http.Handler) http.Handler {
  387. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  388. switch r.Method {
  389. case "GET":
  390. get.ServeHTTP(w, r)
  391. case "POST":
  392. post.ServeHTTP(w, r)
  393. default:
  394. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  395. }
  396. })
  397. }
  398. func debugMiddleware(h http.Handler) http.Handler {
  399. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  400. t0 := time.Now()
  401. h.ServeHTTP(w, r)
  402. if shouldDebugHTTP() {
  403. ms := 1000 * time.Since(t0).Seconds()
  404. // The variable `w` is most likely a *http.response, which we can't do
  405. // much with since it's a non exported type. We can however peek into
  406. // it with reflection to get at the status code and number of bytes
  407. // written.
  408. var status, written int64
  409. if rw := reflect.Indirect(reflect.ValueOf(w)); rw.IsValid() && rw.Kind() == reflect.Struct {
  410. if rf := rw.FieldByName("status"); rf.IsValid() && rf.Kind() == reflect.Int {
  411. status = rf.Int()
  412. }
  413. if rf := rw.FieldByName("written"); rf.IsValid() && rf.Kind() == reflect.Int64 {
  414. written = rf.Int()
  415. }
  416. }
  417. httpl.Debugf("http: %s %q: status %d, %d bytes in %.02f ms", r.Method, r.URL.String(), status, written, ms)
  418. }
  419. })
  420. }
  421. func corsMiddleware(next http.Handler, allowFrameLoading bool) http.Handler {
  422. // Handle CORS headers and CORS OPTIONS request.
  423. // CORS OPTIONS request are typically sent by browser during AJAX preflight
  424. // when the browser initiate a POST request.
  425. //
  426. // As the OPTIONS request is unauthorized, this handler must be the first
  427. // of the chain (hence added at the end).
  428. //
  429. // See https://www.w3.org/TR/cors/ for details.
  430. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  431. // Process OPTIONS requests
  432. if r.Method == "OPTIONS" {
  433. // Add a generous access-control-allow-origin header for CORS requests
  434. w.Header().Add("Access-Control-Allow-Origin", "*")
  435. // Only GET/POST Methods are supported
  436. w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
  437. // Only these headers can be set
  438. w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-API-Key")
  439. // The request is meant to be cached 10 minutes
  440. w.Header().Set("Access-Control-Max-Age", "600")
  441. // Indicate that no content will be returned
  442. w.WriteHeader(204)
  443. return
  444. }
  445. // Other security related headers that should be present.
  446. // https://www.owasp.org/index.php/Security_Headers
  447. if !allowFrameLoading {
  448. // We don't want to be rendered in an <iframe>,
  449. // <frame> or <object>. (Unless we do it ourselves.
  450. // This is also an escape hatch for people who serve
  451. // Syncthing GUI as part of their own website
  452. // through a proxy, so they don't need to set the
  453. // allowFrameLoading bool.)
  454. w.Header().Set("X-Frame-Options", "SAMEORIGIN")
  455. }
  456. // If the browser senses an XSS attack it's allowed to take
  457. // action. (How this would not always be the default I
  458. // don't fully understand.)
  459. w.Header().Set("X-XSS-Protection", "1; mode=block")
  460. // Our content type headers are correct. Don't guess.
  461. w.Header().Set("X-Content-Type-Options", "nosniff")
  462. // For everything else, pass to the next handler
  463. next.ServeHTTP(w, r)
  464. return
  465. })
  466. }
  467. func metricsMiddleware(h http.Handler) http.Handler {
  468. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  469. t := metrics.GetOrRegisterTimer(r.URL.Path, nil)
  470. t0 := time.Now()
  471. h.ServeHTTP(w, r)
  472. t.UpdateSince(t0)
  473. })
  474. }
  475. func redirectToHTTPSMiddleware(h http.Handler) http.Handler {
  476. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  477. if r.TLS == nil {
  478. // Redirect HTTP requests to HTTPS
  479. r.URL.Host = r.Host
  480. r.URL.Scheme = "https"
  481. http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
  482. } else {
  483. h.ServeHTTP(w, r)
  484. }
  485. })
  486. }
  487. func noCacheMiddleware(h http.Handler) http.Handler {
  488. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  489. w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store")
  490. w.Header().Set("Expires", time.Now().UTC().Format(http.TimeFormat))
  491. w.Header().Set("Pragma", "no-cache")
  492. h.ServeHTTP(w, r)
  493. })
  494. }
  495. func withDetailsMiddleware(id protocol.DeviceID, h http.Handler) http.Handler {
  496. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  497. w.Header().Set("X-Syncthing-Version", Version)
  498. w.Header().Set("X-Syncthing-ID", id.String())
  499. h.ServeHTTP(w, r)
  500. })
  501. }
  502. func localhostMiddleware(h http.Handler) http.Handler {
  503. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  504. if addressIsLocalhost(r.Host) {
  505. h.ServeHTTP(w, r)
  506. return
  507. }
  508. http.Error(w, "Host check error", http.StatusForbidden)
  509. })
  510. }
  511. func (s *apiService) whenDebugging(h http.Handler) http.Handler {
  512. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  513. if s.cfg.GUI().Debugging {
  514. h.ServeHTTP(w, r)
  515. return
  516. }
  517. http.Error(w, "Debugging disabled", http.StatusForbidden)
  518. })
  519. }
  520. func (s *apiService) restPing(w http.ResponseWriter, r *http.Request) {
  521. sendJSON(w, map[string]string{"ping": "pong"})
  522. }
  523. func (s *apiService) getJSMetadata(w http.ResponseWriter, r *http.Request) {
  524. meta, _ := json.Marshal(map[string]string{
  525. "deviceID": s.id.String(),
  526. })
  527. w.Header().Set("Content-Type", "application/javascript")
  528. fmt.Fprintf(w, "var metadata = %s;\n", meta)
  529. }
  530. func (s *apiService) getSystemVersion(w http.ResponseWriter, r *http.Request) {
  531. sendJSON(w, map[string]string{
  532. "version": Version,
  533. "codename": Codename,
  534. "longVersion": LongVersion,
  535. "os": runtime.GOOS,
  536. "arch": runtime.GOARCH,
  537. })
  538. }
  539. func (s *apiService) getSystemDebug(w http.ResponseWriter, r *http.Request) {
  540. names := l.Facilities()
  541. enabled := l.FacilityDebugging()
  542. sort.Strings(enabled)
  543. sendJSON(w, map[string]interface{}{
  544. "facilities": names,
  545. "enabled": enabled,
  546. })
  547. }
  548. func (s *apiService) postSystemDebug(w http.ResponseWriter, r *http.Request) {
  549. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  550. q := r.URL.Query()
  551. for _, f := range strings.Split(q.Get("enable"), ",") {
  552. if f == "" || l.ShouldDebug(f) {
  553. continue
  554. }
  555. l.SetDebug(f, true)
  556. l.Infof("Enabled debug data for %q", f)
  557. }
  558. for _, f := range strings.Split(q.Get("disable"), ",") {
  559. if f == "" || !l.ShouldDebug(f) {
  560. continue
  561. }
  562. l.SetDebug(f, false)
  563. l.Infof("Disabled debug data for %q", f)
  564. }
  565. }
  566. func (s *apiService) getDBBrowse(w http.ResponseWriter, r *http.Request) {
  567. qs := r.URL.Query()
  568. folder := qs.Get("folder")
  569. prefix := qs.Get("prefix")
  570. dirsonly := qs.Get("dirsonly") != ""
  571. levels, err := strconv.Atoi(qs.Get("levels"))
  572. if err != nil {
  573. levels = -1
  574. }
  575. sendJSON(w, s.model.GlobalDirectoryTree(folder, prefix, levels, dirsonly))
  576. }
  577. func (s *apiService) getDBCompletion(w http.ResponseWriter, r *http.Request) {
  578. var qs = r.URL.Query()
  579. var folder = qs.Get("folder")
  580. var deviceStr = qs.Get("device")
  581. device, err := protocol.DeviceIDFromString(deviceStr)
  582. if err != nil {
  583. http.Error(w, err.Error(), 500)
  584. return
  585. }
  586. sendJSON(w, jsonCompletion(s.model.Completion(device, folder)))
  587. }
  588. func jsonCompletion(comp model.FolderCompletion) map[string]interface{} {
  589. return map[string]interface{}{
  590. "completion": comp.CompletionPct,
  591. "needBytes": comp.NeedBytes,
  592. "needItems": comp.NeedItems,
  593. "globalBytes": comp.GlobalBytes,
  594. "needDeletes": comp.NeedDeletes,
  595. }
  596. }
  597. func (s *apiService) getDBStatus(w http.ResponseWriter, r *http.Request) {
  598. qs := r.URL.Query()
  599. folder := qs.Get("folder")
  600. if sum, err := folderSummary(s.cfg, s.model, folder); err != nil {
  601. http.Error(w, err.Error(), http.StatusNotFound)
  602. } else {
  603. sendJSON(w, sum)
  604. }
  605. }
  606. func folderSummary(cfg configIntf, m modelIntf, folder string) (map[string]interface{}, error) {
  607. var res = make(map[string]interface{})
  608. pullErrors, err := m.PullErrors(folder)
  609. if err != nil && err != model.ErrFolderPaused {
  610. // Stats from the db can still be obtained if the folder is just paused
  611. return nil, err
  612. }
  613. res["pullErrors"] = len(pullErrors)
  614. res["invalid"] = "" // Deprecated, retains external API for now
  615. global := m.GlobalSize(folder)
  616. res["globalFiles"], res["globalDirectories"], res["globalSymlinks"], res["globalDeleted"], res["globalBytes"] = global.Files, global.Directories, global.Symlinks, global.Deleted, global.Bytes
  617. local := m.LocalSize(folder)
  618. res["localFiles"], res["localDirectories"], res["localSymlinks"], res["localDeleted"], res["localBytes"] = local.Files, local.Directories, local.Symlinks, local.Deleted, local.Bytes
  619. need := m.NeedSize(folder)
  620. res["needFiles"], res["needDirectories"], res["needSymlinks"], res["needDeletes"], res["needBytes"] = need.Files, need.Directories, need.Symlinks, need.Deleted, need.Bytes
  621. if cfg.Folders()[folder].Type == config.FolderTypeReceiveOnly {
  622. // Add statistics for things that have changed locally in a receive
  623. // only folder.
  624. ro := m.ReceiveOnlyChangedSize(folder)
  625. res["receiveOnlyChangedFiles"] = ro.Files
  626. res["receiveOnlyChangedDirectories"] = ro.Directories
  627. res["receiveOnlyChangedSymlinks"] = ro.Symlinks
  628. res["receiveOnlyChangedDeletes"] = ro.Deleted
  629. res["receiveOnlyChangedBytes"] = ro.Bytes
  630. }
  631. res["inSyncFiles"], res["inSyncBytes"] = global.Files-need.Files, global.Bytes-need.Bytes
  632. res["state"], res["stateChanged"], err = m.State(folder)
  633. if err != nil {
  634. res["error"] = err.Error()
  635. }
  636. ourSeq, _ := m.CurrentSequence(folder)
  637. remoteSeq, _ := m.RemoteSequence(folder)
  638. res["version"] = ourSeq + remoteSeq // legacy
  639. res["sequence"] = ourSeq + remoteSeq // new name
  640. ignorePatterns, _, _ := m.GetIgnores(folder)
  641. res["ignorePatterns"] = false
  642. for _, line := range ignorePatterns {
  643. if len(line) > 0 && !strings.HasPrefix(line, "//") {
  644. res["ignorePatterns"] = true
  645. break
  646. }
  647. }
  648. err = m.WatchError(folder)
  649. if err != nil {
  650. res["watchError"] = err.Error()
  651. }
  652. return res, nil
  653. }
  654. func (s *apiService) postDBOverride(w http.ResponseWriter, r *http.Request) {
  655. var qs = r.URL.Query()
  656. var folder = qs.Get("folder")
  657. go s.model.Override(folder)
  658. }
  659. func (s *apiService) postDBRevert(w http.ResponseWriter, r *http.Request) {
  660. var qs = r.URL.Query()
  661. var folder = qs.Get("folder")
  662. go s.model.Revert(folder)
  663. }
  664. func getPagingParams(qs url.Values) (int, int) {
  665. page, err := strconv.Atoi(qs.Get("page"))
  666. if err != nil || page < 1 {
  667. page = 1
  668. }
  669. perpage, err := strconv.Atoi(qs.Get("perpage"))
  670. if err != nil || perpage < 1 {
  671. perpage = 1 << 16
  672. }
  673. return page, perpage
  674. }
  675. func (s *apiService) getDBNeed(w http.ResponseWriter, r *http.Request) {
  676. qs := r.URL.Query()
  677. folder := qs.Get("folder")
  678. page, perpage := getPagingParams(qs)
  679. progress, queued, rest := s.model.NeedFolderFiles(folder, page, perpage)
  680. // Convert the struct to a more loose structure, and inject the size.
  681. sendJSON(w, map[string]interface{}{
  682. "progress": toNeedSlice(progress),
  683. "queued": toNeedSlice(queued),
  684. "rest": toNeedSlice(rest),
  685. "page": page,
  686. "perpage": perpage,
  687. })
  688. }
  689. func (s *apiService) getDBRemoteNeed(w http.ResponseWriter, r *http.Request) {
  690. qs := r.URL.Query()
  691. folder := qs.Get("folder")
  692. device := qs.Get("device")
  693. deviceID, err := protocol.DeviceIDFromString(device)
  694. if err != nil {
  695. http.Error(w, err.Error(), 500)
  696. return
  697. }
  698. page, perpage := getPagingParams(qs)
  699. if files, err := s.model.RemoteNeedFolderFiles(deviceID, folder, page, perpage); err != nil {
  700. http.Error(w, err.Error(), http.StatusNotFound)
  701. } else {
  702. sendJSON(w, map[string]interface{}{
  703. "files": toNeedSlice(files),
  704. "page": page,
  705. "perpage": perpage,
  706. })
  707. }
  708. }
  709. func (s *apiService) getSystemConnections(w http.ResponseWriter, r *http.Request) {
  710. sendJSON(w, s.model.ConnectionStats())
  711. }
  712. func (s *apiService) getDeviceStats(w http.ResponseWriter, r *http.Request) {
  713. sendJSON(w, s.model.DeviceStatistics())
  714. }
  715. func (s *apiService) getFolderStats(w http.ResponseWriter, r *http.Request) {
  716. sendJSON(w, s.model.FolderStatistics())
  717. }
  718. func (s *apiService) getDBFile(w http.ResponseWriter, r *http.Request) {
  719. qs := r.URL.Query()
  720. folder := qs.Get("folder")
  721. file := qs.Get("file")
  722. gf, gfOk := s.model.CurrentGlobalFile(folder, file)
  723. lf, lfOk := s.model.CurrentFolderFile(folder, file)
  724. if !(gfOk || lfOk) {
  725. // This file for sure does not exist.
  726. http.Error(w, "No such object in the index", http.StatusNotFound)
  727. return
  728. }
  729. av := s.model.Availability(folder, gf, protocol.BlockInfo{})
  730. sendJSON(w, map[string]interface{}{
  731. "global": jsonFileInfo(gf),
  732. "local": jsonFileInfo(lf),
  733. "availability": av,
  734. })
  735. }
  736. func (s *apiService) getSystemConfig(w http.ResponseWriter, r *http.Request) {
  737. sendJSON(w, s.cfg.RawCopy())
  738. }
  739. func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
  740. s.systemConfigMut.Lock()
  741. defer s.systemConfigMut.Unlock()
  742. to, err := config.ReadJSON(r.Body, myID)
  743. r.Body.Close()
  744. if err != nil {
  745. l.Warnln("Decoding posted config:", err)
  746. http.Error(w, err.Error(), http.StatusBadRequest)
  747. return
  748. }
  749. if to.GUI.Password != s.cfg.GUI().Password {
  750. if to.GUI.Password != "" && !bcryptExpr.MatchString(to.GUI.Password) {
  751. hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0)
  752. if err != nil {
  753. l.Warnln("bcrypting password:", err)
  754. http.Error(w, err.Error(), http.StatusInternalServerError)
  755. return
  756. }
  757. to.GUI.Password = string(hash)
  758. }
  759. }
  760. // Activate and save. Wait for the configuration to become active before
  761. // completing the request.
  762. if wg, err := s.cfg.Replace(to); err != nil {
  763. l.Warnln("Replacing config:", err)
  764. http.Error(w, err.Error(), http.StatusInternalServerError)
  765. return
  766. } else {
  767. wg.Wait()
  768. }
  769. if err := s.cfg.Save(); err != nil {
  770. l.Warnln("Saving config:", err)
  771. http.Error(w, err.Error(), http.StatusInternalServerError)
  772. return
  773. }
  774. }
  775. func (s *apiService) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
  776. sendJSON(w, map[string]bool{"configInSync": !s.cfg.RequiresRestart()})
  777. }
  778. func (s *apiService) postSystemRestart(w http.ResponseWriter, r *http.Request) {
  779. s.flushResponse(`{"ok": "restarting"}`, w)
  780. go restart()
  781. }
  782. func (s *apiService) postSystemReset(w http.ResponseWriter, r *http.Request) {
  783. var qs = r.URL.Query()
  784. folder := qs.Get("folder")
  785. if len(folder) > 0 {
  786. if _, ok := s.cfg.Folders()[folder]; !ok {
  787. http.Error(w, "Invalid folder ID", 500)
  788. return
  789. }
  790. }
  791. if len(folder) == 0 {
  792. // Reset all folders.
  793. for folder := range s.cfg.Folders() {
  794. s.model.ResetFolder(folder)
  795. }
  796. s.flushResponse(`{"ok": "resetting database"}`, w)
  797. } else {
  798. // Reset a specific folder, assuming it's supposed to exist.
  799. s.model.ResetFolder(folder)
  800. s.flushResponse(`{"ok": "resetting folder `+folder+`"}`, w)
  801. }
  802. go restart()
  803. }
  804. func (s *apiService) postSystemShutdown(w http.ResponseWriter, r *http.Request) {
  805. s.flushResponse(`{"ok": "shutting down"}`, w)
  806. go shutdown()
  807. }
  808. func (s *apiService) flushResponse(resp string, w http.ResponseWriter) {
  809. w.Write([]byte(resp + "\n"))
  810. f := w.(http.Flusher)
  811. f.Flush()
  812. }
  813. func (s *apiService) getSystemStatus(w http.ResponseWriter, r *http.Request) {
  814. var m runtime.MemStats
  815. runtime.ReadMemStats(&m)
  816. tilde, _ := fs.ExpandTilde("~")
  817. res := make(map[string]interface{})
  818. res["myID"] = myID.String()
  819. res["goroutines"] = runtime.NumGoroutine()
  820. res["alloc"] = m.Alloc
  821. res["sys"] = m.Sys - m.HeapReleased
  822. res["tilde"] = tilde
  823. if s.cfg.Options().LocalAnnEnabled || s.cfg.Options().GlobalAnnEnabled {
  824. res["discoveryEnabled"] = true
  825. discoErrors := make(map[string]string)
  826. discoMethods := 0
  827. for disco, err := range s.discoverer.ChildErrors() {
  828. discoMethods++
  829. if err != nil {
  830. discoErrors[disco] = err.Error()
  831. }
  832. }
  833. res["discoveryMethods"] = discoMethods
  834. res["discoveryErrors"] = discoErrors
  835. }
  836. res["connectionServiceStatus"] = s.connectionsService.Status()
  837. // cpuUsage.Rate() is in milliseconds per second, so dividing by ten
  838. // gives us percent
  839. res["cpuPercent"] = s.cpu.Rate() / 10 / float64(runtime.NumCPU())
  840. res["pathSeparator"] = string(filepath.Separator)
  841. res["urVersionMax"] = usageReportVersion
  842. res["uptime"] = int(time.Since(startTime).Seconds())
  843. res["startTime"] = startTime
  844. res["guiAddressOverridden"] = s.cfg.GUI().IsOverridden()
  845. sendJSON(w, res)
  846. }
  847. func (s *apiService) getSystemError(w http.ResponseWriter, r *http.Request) {
  848. sendJSON(w, map[string][]logger.Line{
  849. "errors": s.guiErrors.Since(time.Time{}),
  850. })
  851. }
  852. func (s *apiService) postSystemError(w http.ResponseWriter, r *http.Request) {
  853. bs, _ := ioutil.ReadAll(r.Body)
  854. r.Body.Close()
  855. l.Warnln(string(bs))
  856. }
  857. func (s *apiService) postSystemErrorClear(w http.ResponseWriter, r *http.Request) {
  858. s.guiErrors.Clear()
  859. }
  860. func (s *apiService) getSystemLog(w http.ResponseWriter, r *http.Request) {
  861. q := r.URL.Query()
  862. since, err := time.Parse(time.RFC3339, q.Get("since"))
  863. if err != nil {
  864. l.Debugln(err)
  865. }
  866. sendJSON(w, map[string][]logger.Line{
  867. "messages": s.systemLog.Since(since),
  868. })
  869. }
  870. func (s *apiService) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
  871. q := r.URL.Query()
  872. since, err := time.Parse(time.RFC3339, q.Get("since"))
  873. if err != nil {
  874. l.Debugln(err)
  875. }
  876. w.Header().Set("Content-Type", "text/plain; charset=utf-8")
  877. for _, line := range s.systemLog.Since(since) {
  878. fmt.Fprintf(w, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
  879. }
  880. }
  881. type fileEntry struct {
  882. name string
  883. data []byte
  884. }
  885. func (s *apiService) getSupportBundle(w http.ResponseWriter, r *http.Request) {
  886. var files []fileEntry
  887. // Redacted configuration as a JSON
  888. if jsonConfig, err := json.MarshalIndent(getRedactedConfig(s), "", " "); err == nil {
  889. files = append(files, fileEntry{name: "config.json.txt", data: jsonConfig})
  890. } else {
  891. l.Warnln("Support bundle: failed to create config.json:", err)
  892. }
  893. // Log as a text
  894. var buflog bytes.Buffer
  895. for _, line := range s.systemLog.Since(time.Time{}) {
  896. fmt.Fprintf(&buflog, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
  897. }
  898. files = append(files, fileEntry{name: "log-inmemory.txt", data: buflog.Bytes()})
  899. // Errors as a JSON
  900. if errs := s.guiErrors.Since(time.Time{}); len(errs) > 0 {
  901. if jsonError, err := json.MarshalIndent(errs, "", " "); err != nil {
  902. files = append(files, fileEntry{name: "errors.json.txt", data: jsonError})
  903. } else {
  904. l.Warnln("Support bundle: failed to create errors.json:", err)
  905. }
  906. }
  907. // Panic files
  908. if panicFiles, err := filepath.Glob(filepath.Join(baseDirs["config"], "panic*")); err == nil {
  909. for _, f := range panicFiles {
  910. if panicFile, err := ioutil.ReadFile(f); err != nil {
  911. l.Warnf("Support bundle: failed to load %s: %s", filepath.Base(f), err)
  912. } else {
  913. files = append(files, fileEntry{name: filepath.Base(f), data: panicFile})
  914. }
  915. }
  916. }
  917. // Archived log (default on Windows)
  918. if logFile, err := ioutil.ReadFile(locations[locLogFile]); err == nil {
  919. files = append(files, fileEntry{name: "log-ondisk.txt", data: logFile})
  920. }
  921. // Version and platform information as a JSON
  922. if versionPlatform, err := json.MarshalIndent(map[string]string{
  923. "now": time.Now().Format(time.RFC3339),
  924. "version": Version,
  925. "codename": Codename,
  926. "longVersion": LongVersion,
  927. "os": runtime.GOOS,
  928. "arch": runtime.GOARCH,
  929. }, "", " "); err == nil {
  930. files = append(files, fileEntry{name: "version-platform.json.txt", data: versionPlatform})
  931. } else {
  932. l.Warnln("Failed to create versionPlatform.json: ", err)
  933. }
  934. // Report Data as a JSON
  935. if usageReportingData, err := json.MarshalIndent(reportData(s.cfg, s.model, s.connectionsService, usageReportVersion, true), "", " "); err != nil {
  936. l.Warnln("Support bundle: failed to create versionPlatform.json:", err)
  937. } else {
  938. files = append(files, fileEntry{name: "usage-reporting.json.txt", data: usageReportingData})
  939. }
  940. // Heap and CPU Proofs as a pprof extension
  941. var heapBuffer, cpuBuffer bytes.Buffer
  942. filename := fmt.Sprintf("syncthing-heap-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, Version, time.Now().Format("150405")) // hhmmss
  943. runtime.GC()
  944. pprof.WriteHeapProfile(&heapBuffer)
  945. files = append(files, fileEntry{name: filename, data: heapBuffer.Bytes()})
  946. const duration = 4 * time.Second
  947. filename = fmt.Sprintf("syncthing-cpu-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, Version, time.Now().Format("150405")) // hhmmss
  948. pprof.StartCPUProfile(&cpuBuffer)
  949. time.Sleep(duration)
  950. pprof.StopCPUProfile()
  951. files = append(files, fileEntry{name: filename, data: cpuBuffer.Bytes()})
  952. // Add buffer files to buffer zip
  953. var zipFilesBuffer bytes.Buffer
  954. if err := writeZip(&zipFilesBuffer, files); err != nil {
  955. l.Warnln("Support bundle: failed to create support bundle zip:", err)
  956. http.Error(w, err.Error(), http.StatusInternalServerError)
  957. return
  958. }
  959. // Set zip file name and path
  960. zipFileName := fmt.Sprintf("support-bundle-%s-%s.zip", s.id.Short().String(), time.Now().Format("2006-01-02T150405"))
  961. zipFilePath := filepath.Join(baseDirs["config"], zipFileName)
  962. // Write buffer zip to local zip file (back up)
  963. if err := ioutil.WriteFile(zipFilePath, zipFilesBuffer.Bytes(), 0600); err != nil {
  964. l.Warnln("Support bundle: support bundle zip could not be created:", err)
  965. }
  966. // Serve the buffer zip to client for download
  967. w.Header().Set("Content-Type", "application/zip")
  968. w.Header().Set("Content-Disposition", "attachment; filename="+zipFileName)
  969. io.Copy(w, &zipFilesBuffer)
  970. }
  971. func (s *apiService) getSystemHTTPMetrics(w http.ResponseWriter, r *http.Request) {
  972. stats := make(map[string]interface{})
  973. metrics.Each(func(name string, intf interface{}) {
  974. if m, ok := intf.(*metrics.StandardTimer); ok {
  975. pct := m.Percentiles([]float64{0.50, 0.95, 0.99})
  976. for i := range pct {
  977. pct[i] /= 1e6 // ns to ms
  978. }
  979. stats[name] = map[string]interface{}{
  980. "count": m.Count(),
  981. "sumMs": m.Sum() / 1e6, // ns to ms
  982. "ratesPerS": []float64{m.Rate1(), m.Rate5(), m.Rate15()},
  983. "percentilesMs": pct,
  984. }
  985. }
  986. })
  987. bs, _ := json.MarshalIndent(stats, "", " ")
  988. w.Write(bs)
  989. }
  990. func (s *apiService) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
  991. devices := make(map[string]discover.CacheEntry)
  992. if s.discoverer != nil {
  993. // Device ids can't be marshalled as keys so we need to manually
  994. // rebuild this map using strings. Discoverer may be nil if discovery
  995. // has not started yet.
  996. for device, entry := range s.discoverer.Cache() {
  997. devices[device.String()] = entry
  998. }
  999. }
  1000. sendJSON(w, devices)
  1001. }
  1002. func (s *apiService) getReport(w http.ResponseWriter, r *http.Request) {
  1003. version := usageReportVersion
  1004. if val, _ := strconv.Atoi(r.URL.Query().Get("version")); val > 0 {
  1005. version = val
  1006. }
  1007. sendJSON(w, reportData(s.cfg, s.model, s.connectionsService, version, true))
  1008. }
  1009. func (s *apiService) getRandomString(w http.ResponseWriter, r *http.Request) {
  1010. length := 32
  1011. if val, _ := strconv.Atoi(r.URL.Query().Get("length")); val > 0 {
  1012. length = val
  1013. }
  1014. str := rand.String(length)
  1015. sendJSON(w, map[string]string{"random": str})
  1016. }
  1017. func (s *apiService) getDBIgnores(w http.ResponseWriter, r *http.Request) {
  1018. qs := r.URL.Query()
  1019. folder := qs.Get("folder")
  1020. ignores, patterns, err := s.model.GetIgnores(folder)
  1021. if err != nil {
  1022. http.Error(w, err.Error(), 500)
  1023. return
  1024. }
  1025. sendJSON(w, map[string][]string{
  1026. "ignore": ignores,
  1027. "expanded": patterns,
  1028. })
  1029. }
  1030. func (s *apiService) postDBIgnores(w http.ResponseWriter, r *http.Request) {
  1031. qs := r.URL.Query()
  1032. bs, err := ioutil.ReadAll(r.Body)
  1033. r.Body.Close()
  1034. if err != nil {
  1035. http.Error(w, err.Error(), 500)
  1036. return
  1037. }
  1038. var data map[string][]string
  1039. err = json.Unmarshal(bs, &data)
  1040. if err != nil {
  1041. http.Error(w, err.Error(), 500)
  1042. return
  1043. }
  1044. err = s.model.SetIgnores(qs.Get("folder"), data["ignore"])
  1045. if err != nil {
  1046. http.Error(w, err.Error(), 500)
  1047. return
  1048. }
  1049. s.getDBIgnores(w, r)
  1050. }
  1051. func (s *apiService) getIndexEvents(w http.ResponseWriter, r *http.Request) {
  1052. s.fss.gotEventRequest()
  1053. mask := s.getEventMask(r.URL.Query().Get("events"))
  1054. sub := s.getEventSub(mask)
  1055. s.getEvents(w, r, sub)
  1056. }
  1057. func (s *apiService) getDiskEvents(w http.ResponseWriter, r *http.Request) {
  1058. sub := s.getEventSub(diskEventMask)
  1059. s.getEvents(w, r, sub)
  1060. }
  1061. func (s *apiService) getEvents(w http.ResponseWriter, r *http.Request, eventSub events.BufferedSubscription) {
  1062. qs := r.URL.Query()
  1063. sinceStr := qs.Get("since")
  1064. limitStr := qs.Get("limit")
  1065. timeoutStr := qs.Get("timeout")
  1066. since, _ := strconv.Atoi(sinceStr)
  1067. limit, _ := strconv.Atoi(limitStr)
  1068. timeout := defaultEventTimeout
  1069. if timeoutSec, timeoutErr := strconv.Atoi(timeoutStr); timeoutErr == nil && timeoutSec >= 0 { // 0 is a valid timeout
  1070. timeout = time.Duration(timeoutSec) * time.Second
  1071. }
  1072. // Flush before blocking, to indicate that we've received the request and
  1073. // that it should not be retried. Must set Content-Type header before
  1074. // flushing.
  1075. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  1076. f := w.(http.Flusher)
  1077. f.Flush()
  1078. // If there are no events available return an empty slice, as this gets serialized as `[]`
  1079. evs := eventSub.Since(since, []events.Event{}, timeout)
  1080. if 0 < limit && limit < len(evs) {
  1081. evs = evs[len(evs)-limit:]
  1082. }
  1083. sendJSON(w, evs)
  1084. }
  1085. func (s *apiService) getEventMask(evs string) events.EventType {
  1086. eventMask := defaultEventMask
  1087. if evs != "" {
  1088. eventList := strings.Split(evs, ",")
  1089. eventMask = 0
  1090. for _, ev := range eventList {
  1091. eventMask |= events.UnmarshalEventType(strings.TrimSpace(ev))
  1092. }
  1093. }
  1094. return eventMask
  1095. }
  1096. func (s *apiService) getEventSub(mask events.EventType) events.BufferedSubscription {
  1097. s.eventSubsMut.Lock()
  1098. bufsub, ok := s.eventSubs[mask]
  1099. if !ok {
  1100. evsub := events.Default.Subscribe(mask)
  1101. bufsub = events.NewBufferedSubscription(evsub, eventSubBufferSize)
  1102. s.eventSubs[mask] = bufsub
  1103. }
  1104. s.eventSubsMut.Unlock()
  1105. return bufsub
  1106. }
  1107. func (s *apiService) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
  1108. if noUpgradeFromEnv {
  1109. http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
  1110. return
  1111. }
  1112. opts := s.cfg.Options()
  1113. rel, err := upgrade.LatestRelease(opts.ReleasesURL, Version, opts.UpgradeToPreReleases)
  1114. if err != nil {
  1115. http.Error(w, err.Error(), 500)
  1116. return
  1117. }
  1118. res := make(map[string]interface{})
  1119. res["running"] = Version
  1120. res["latest"] = rel.Tag
  1121. res["newer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.Newer
  1122. res["majorNewer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.MajorNewer
  1123. sendJSON(w, res)
  1124. }
  1125. func (s *apiService) getDeviceID(w http.ResponseWriter, r *http.Request) {
  1126. qs := r.URL.Query()
  1127. idStr := qs.Get("id")
  1128. id, err := protocol.DeviceIDFromString(idStr)
  1129. if err == nil {
  1130. sendJSON(w, map[string]string{
  1131. "id": id.String(),
  1132. })
  1133. } else {
  1134. sendJSON(w, map[string]string{
  1135. "error": err.Error(),
  1136. })
  1137. }
  1138. }
  1139. func (s *apiService) getLang(w http.ResponseWriter, r *http.Request) {
  1140. lang := r.Header.Get("Accept-Language")
  1141. var langs []string
  1142. for _, l := range strings.Split(lang, ",") {
  1143. parts := strings.SplitN(l, ";", 2)
  1144. langs = append(langs, strings.ToLower(strings.TrimSpace(parts[0])))
  1145. }
  1146. sendJSON(w, langs)
  1147. }
  1148. func (s *apiService) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
  1149. opts := s.cfg.Options()
  1150. rel, err := upgrade.LatestRelease(opts.ReleasesURL, Version, opts.UpgradeToPreReleases)
  1151. if err != nil {
  1152. l.Warnln("getting latest release:", err)
  1153. http.Error(w, err.Error(), 500)
  1154. return
  1155. }
  1156. if upgrade.CompareVersions(rel.Tag, Version) > upgrade.Equal {
  1157. err = upgrade.To(rel)
  1158. if err != nil {
  1159. l.Warnln("upgrading:", err)
  1160. http.Error(w, err.Error(), 500)
  1161. return
  1162. }
  1163. s.flushResponse(`{"ok": "restarting"}`, w)
  1164. l.Infoln("Upgrading")
  1165. stop <- exitUpgrading
  1166. }
  1167. }
  1168. func (s *apiService) makeDevicePauseHandler(paused bool) http.HandlerFunc {
  1169. return func(w http.ResponseWriter, r *http.Request) {
  1170. var qs = r.URL.Query()
  1171. var deviceStr = qs.Get("device")
  1172. var cfgs []config.DeviceConfiguration
  1173. if deviceStr == "" {
  1174. for _, cfg := range s.cfg.Devices() {
  1175. cfg.Paused = paused
  1176. cfgs = append(cfgs, cfg)
  1177. }
  1178. } else {
  1179. device, err := protocol.DeviceIDFromString(deviceStr)
  1180. if err != nil {
  1181. http.Error(w, err.Error(), 500)
  1182. return
  1183. }
  1184. cfg, ok := s.cfg.Devices()[device]
  1185. if !ok {
  1186. http.Error(w, "not found", http.StatusNotFound)
  1187. return
  1188. }
  1189. cfg.Paused = paused
  1190. cfgs = append(cfgs, cfg)
  1191. }
  1192. if _, err := s.cfg.SetDevices(cfgs); err != nil {
  1193. http.Error(w, err.Error(), 500)
  1194. }
  1195. }
  1196. }
  1197. func (s *apiService) postDBScan(w http.ResponseWriter, r *http.Request) {
  1198. qs := r.URL.Query()
  1199. folder := qs.Get("folder")
  1200. if folder != "" {
  1201. subs := qs["sub"]
  1202. err := s.model.ScanFolderSubdirs(folder, subs)
  1203. if err != nil {
  1204. http.Error(w, err.Error(), 500)
  1205. return
  1206. }
  1207. nextStr := qs.Get("next")
  1208. next, err := strconv.Atoi(nextStr)
  1209. if err == nil {
  1210. s.model.DelayScan(folder, time.Duration(next)*time.Second)
  1211. }
  1212. } else {
  1213. errors := s.model.ScanFolders()
  1214. if len(errors) > 0 {
  1215. http.Error(w, "Error scanning folders", 500)
  1216. sendJSON(w, errors)
  1217. return
  1218. }
  1219. }
  1220. }
  1221. func (s *apiService) postDBPrio(w http.ResponseWriter, r *http.Request) {
  1222. qs := r.URL.Query()
  1223. folder := qs.Get("folder")
  1224. file := qs.Get("file")
  1225. s.model.BringToFront(folder, file)
  1226. s.getDBNeed(w, r)
  1227. }
  1228. func (s *apiService) getQR(w http.ResponseWriter, r *http.Request) {
  1229. var qs = r.URL.Query()
  1230. var text = qs.Get("text")
  1231. code, err := qr.Encode(text, qr.M)
  1232. if err != nil {
  1233. http.Error(w, "Invalid", 500)
  1234. return
  1235. }
  1236. w.Header().Set("Content-Type", "image/png")
  1237. w.Write(code.PNG())
  1238. }
  1239. func (s *apiService) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
  1240. tot := map[string]float64{}
  1241. count := map[string]float64{}
  1242. for _, folder := range s.cfg.Folders() {
  1243. for _, device := range folder.DeviceIDs() {
  1244. deviceStr := device.String()
  1245. if _, ok := s.model.Connection(device); ok {
  1246. tot[deviceStr] += s.model.Completion(device, folder.ID).CompletionPct
  1247. } else {
  1248. tot[deviceStr] = 0
  1249. }
  1250. count[deviceStr]++
  1251. }
  1252. }
  1253. comp := map[string]int{}
  1254. for device := range tot {
  1255. comp[device] = int(tot[device] / count[device])
  1256. }
  1257. sendJSON(w, comp)
  1258. }
  1259. func (s *apiService) getFolderVersions(w http.ResponseWriter, r *http.Request) {
  1260. qs := r.URL.Query()
  1261. versions, err := s.model.GetFolderVersions(qs.Get("folder"))
  1262. if err != nil {
  1263. http.Error(w, err.Error(), 500)
  1264. return
  1265. }
  1266. sendJSON(w, versions)
  1267. }
  1268. func (s *apiService) postFolderVersionsRestore(w http.ResponseWriter, r *http.Request) {
  1269. qs := r.URL.Query()
  1270. bs, err := ioutil.ReadAll(r.Body)
  1271. r.Body.Close()
  1272. if err != nil {
  1273. http.Error(w, err.Error(), 500)
  1274. return
  1275. }
  1276. var versions map[string]time.Time
  1277. err = json.Unmarshal(bs, &versions)
  1278. if err != nil {
  1279. http.Error(w, err.Error(), 500)
  1280. return
  1281. }
  1282. ferr, err := s.model.RestoreFolderVersions(qs.Get("folder"), versions)
  1283. if err != nil {
  1284. http.Error(w, err.Error(), 500)
  1285. return
  1286. }
  1287. sendJSON(w, ferr)
  1288. }
  1289. func (s *apiService) getPullErrors(w http.ResponseWriter, r *http.Request) {
  1290. qs := r.URL.Query()
  1291. folder := qs.Get("folder")
  1292. page, perpage := getPagingParams(qs)
  1293. errors, err := s.model.PullErrors(folder)
  1294. if err != nil {
  1295. http.Error(w, err.Error(), http.StatusNotFound)
  1296. return
  1297. }
  1298. start := (page - 1) * perpage
  1299. if start >= len(errors) {
  1300. errors = nil
  1301. } else {
  1302. errors = errors[start:]
  1303. if perpage < len(errors) {
  1304. errors = errors[:perpage]
  1305. }
  1306. }
  1307. sendJSON(w, map[string]interface{}{
  1308. "folder": folder,
  1309. "errors": errors,
  1310. "page": page,
  1311. "perpage": perpage,
  1312. })
  1313. }
  1314. func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
  1315. qs := r.URL.Query()
  1316. current := qs.Get("current")
  1317. // Default value or in case of error unmarshalling ends up being basic fs.
  1318. var fsType fs.FilesystemType
  1319. fsType.UnmarshalText([]byte(qs.Get("filesystem")))
  1320. sendJSON(w, browseFiles(current, fsType))
  1321. }
  1322. func browseFiles(current string, fsType fs.FilesystemType) []string {
  1323. if current == "" {
  1324. filesystem := fs.NewFilesystem(fsType, "")
  1325. if roots, err := filesystem.Roots(); err == nil {
  1326. return roots
  1327. }
  1328. return nil
  1329. }
  1330. search, _ := fs.ExpandTilde(current)
  1331. pathSeparator := string(fs.PathSeparator)
  1332. if strings.HasSuffix(current, pathSeparator) && !strings.HasSuffix(search, pathSeparator) {
  1333. search = search + pathSeparator
  1334. }
  1335. searchDir := filepath.Dir(search)
  1336. // The searchFile should be the last component of search, or empty if it
  1337. // ends with a path separator
  1338. var searchFile string
  1339. if !strings.HasSuffix(search, pathSeparator) {
  1340. searchFile = filepath.Base(search)
  1341. }
  1342. fs := fs.NewFilesystem(fsType, searchDir)
  1343. subdirectories, _ := fs.Glob(searchFile + "*")
  1344. ret := make([]string, 0, len(subdirectories))
  1345. for _, subdirectory := range subdirectories {
  1346. info, err := fs.Stat(subdirectory)
  1347. if err == nil && info.IsDir() {
  1348. ret = append(ret, filepath.Join(searchDir, subdirectory)+pathSeparator)
  1349. }
  1350. }
  1351. return ret
  1352. }
  1353. func (s *apiService) getCPUProf(w http.ResponseWriter, r *http.Request) {
  1354. duration, err := time.ParseDuration(r.FormValue("duration"))
  1355. if err != nil {
  1356. duration = 30 * time.Second
  1357. }
  1358. filename := fmt.Sprintf("syncthing-cpu-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, Version, time.Now().Format("150405")) // hhmmss
  1359. w.Header().Set("Content-Type", "application/octet-stream")
  1360. w.Header().Set("Content-Disposition", "attachment; filename="+filename)
  1361. pprof.StartCPUProfile(w)
  1362. time.Sleep(duration)
  1363. pprof.StopCPUProfile()
  1364. }
  1365. func (s *apiService) getHeapProf(w http.ResponseWriter, r *http.Request) {
  1366. filename := fmt.Sprintf("syncthing-heap-%s-%s-%s-%s.pprof", runtime.GOOS, runtime.GOARCH, Version, time.Now().Format("150405")) // hhmmss
  1367. w.Header().Set("Content-Type", "application/octet-stream")
  1368. w.Header().Set("Content-Disposition", "attachment; filename="+filename)
  1369. runtime.GC()
  1370. pprof.WriteHeapProfile(w)
  1371. }
  1372. func toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
  1373. res := make([]jsonDBFileInfo, len(fs))
  1374. for i, f := range fs {
  1375. res[i] = jsonDBFileInfo(f)
  1376. }
  1377. return res
  1378. }
  1379. // Type wrappers for nice JSON serialization
  1380. type jsonFileInfo protocol.FileInfo
  1381. func (f jsonFileInfo) MarshalJSON() ([]byte, error) {
  1382. return json.Marshal(map[string]interface{}{
  1383. "name": f.Name,
  1384. "type": f.Type,
  1385. "size": f.Size,
  1386. "permissions": fmt.Sprintf("%#o", f.Permissions),
  1387. "deleted": f.Deleted,
  1388. "invalid": protocol.FileInfo(f).IsInvalid(),
  1389. "ignored": protocol.FileInfo(f).IsIgnored(),
  1390. "mustRescan": protocol.FileInfo(f).MustRescan(),
  1391. "noPermissions": f.NoPermissions,
  1392. "modified": protocol.FileInfo(f).ModTime(),
  1393. "modifiedBy": f.ModifiedBy.String(),
  1394. "sequence": f.Sequence,
  1395. "numBlocks": len(f.Blocks),
  1396. "version": jsonVersionVector(f.Version),
  1397. "localFlags": f.LocalFlags,
  1398. })
  1399. }
  1400. type jsonDBFileInfo db.FileInfoTruncated
  1401. func (f jsonDBFileInfo) MarshalJSON() ([]byte, error) {
  1402. return json.Marshal(map[string]interface{}{
  1403. "name": f.Name,
  1404. "type": f.Type.String(),
  1405. "size": f.Size,
  1406. "permissions": fmt.Sprintf("%#o", f.Permissions),
  1407. "deleted": f.Deleted,
  1408. "invalid": db.FileInfoTruncated(f).IsInvalid(),
  1409. "ignored": db.FileInfoTruncated(f).IsIgnored(),
  1410. "mustRescan": db.FileInfoTruncated(f).MustRescan(),
  1411. "noPermissions": f.NoPermissions,
  1412. "modified": db.FileInfoTruncated(f).ModTime(),
  1413. "modifiedBy": f.ModifiedBy.String(),
  1414. "sequence": f.Sequence,
  1415. "numBlocks": nil, // explicitly unknown
  1416. "version": jsonVersionVector(f.Version),
  1417. "localFlags": f.LocalFlags,
  1418. })
  1419. }
  1420. type jsonVersionVector protocol.Vector
  1421. func (v jsonVersionVector) MarshalJSON() ([]byte, error) {
  1422. res := make([]string, len(v.Counters))
  1423. for i, c := range v.Counters {
  1424. res[i] = fmt.Sprintf("%v:%d", c.ID, c.Value)
  1425. }
  1426. return json.Marshal(res)
  1427. }
  1428. func dirNames(dir string) []string {
  1429. fd, err := os.Open(dir)
  1430. if err != nil {
  1431. return nil
  1432. }
  1433. defer fd.Close()
  1434. fis, err := fd.Readdir(-1)
  1435. if err != nil {
  1436. return nil
  1437. }
  1438. var dirs []string
  1439. for _, fi := range fis {
  1440. if fi.IsDir() {
  1441. dirs = append(dirs, filepath.Base(fi.Name()))
  1442. }
  1443. }
  1444. sort.Strings(dirs)
  1445. return dirs
  1446. }
  1447. func addressIsLocalhost(addr string) bool {
  1448. host, _, err := net.SplitHostPort(addr)
  1449. if err != nil {
  1450. // There was no port, so we assume the address was just a hostname
  1451. host = addr
  1452. }
  1453. switch strings.ToLower(host) {
  1454. case "localhost", "localhost.":
  1455. return true
  1456. default:
  1457. ip := net.ParseIP(host)
  1458. if ip == nil {
  1459. // not an IP address
  1460. return false
  1461. }
  1462. return ip.IsLoopback()
  1463. }
  1464. }