gui.go 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153
  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 http://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "bytes"
  9. "compress/gzip"
  10. "crypto/tls"
  11. "encoding/json"
  12. "fmt"
  13. "io/ioutil"
  14. "mime"
  15. "net"
  16. "net/http"
  17. "os"
  18. "path/filepath"
  19. "reflect"
  20. "runtime"
  21. "sort"
  22. "strconv"
  23. "strings"
  24. "time"
  25. "github.com/syncthing/syncthing/lib/auto"
  26. "github.com/syncthing/syncthing/lib/config"
  27. "github.com/syncthing/syncthing/lib/db"
  28. "github.com/syncthing/syncthing/lib/discover"
  29. "github.com/syncthing/syncthing/lib/events"
  30. "github.com/syncthing/syncthing/lib/logger"
  31. "github.com/syncthing/syncthing/lib/model"
  32. "github.com/syncthing/syncthing/lib/osutil"
  33. "github.com/syncthing/syncthing/lib/protocol"
  34. "github.com/syncthing/syncthing/lib/relay"
  35. "github.com/syncthing/syncthing/lib/sync"
  36. "github.com/syncthing/syncthing/lib/tlsutil"
  37. "github.com/syncthing/syncthing/lib/upgrade"
  38. "github.com/vitrun/qart/qr"
  39. "golang.org/x/crypto/bcrypt"
  40. )
  41. var (
  42. configInSync = true
  43. startTime = time.Now()
  44. )
  45. type apiSvc struct {
  46. id protocol.DeviceID
  47. cfg *config.Wrapper
  48. assetDir string
  49. model *model.Model
  50. eventSub *events.BufferedSubscription
  51. discoverer *discover.CachingMux
  52. relaySvc *relay.Svc
  53. listener net.Listener
  54. fss *folderSummarySvc
  55. stop chan struct{}
  56. systemConfigMut sync.Mutex
  57. guiErrors *logger.Recorder
  58. systemLog *logger.Recorder
  59. }
  60. func newAPISvc(id protocol.DeviceID, cfg *config.Wrapper, assetDir string, m *model.Model, eventSub *events.BufferedSubscription, discoverer *discover.CachingMux, relaySvc *relay.Svc, errors, systemLog *logger.Recorder) (*apiSvc, error) {
  61. svc := &apiSvc{
  62. id: id,
  63. cfg: cfg,
  64. assetDir: assetDir,
  65. model: m,
  66. eventSub: eventSub,
  67. discoverer: discoverer,
  68. relaySvc: relaySvc,
  69. systemConfigMut: sync.NewMutex(),
  70. guiErrors: errors,
  71. systemLog: systemLog,
  72. }
  73. var err error
  74. svc.listener, err = svc.getListener(cfg.GUI())
  75. return svc, err
  76. }
  77. func (s *apiSvc) getListener(cfg config.GUIConfiguration) (net.Listener, error) {
  78. if guiAddress != "" {
  79. // Override from the environment
  80. cfg.Address = guiAddress
  81. }
  82. cert, err := tls.LoadX509KeyPair(locations[locHTTPSCertFile], locations[locHTTPSKeyFile])
  83. if err != nil {
  84. l.Infoln("Loading HTTPS certificate:", err)
  85. l.Infoln("Creating new HTTPS certificate")
  86. // When generating the HTTPS certificate, use the system host name per
  87. // default. If that isn't available, use the "syncthing" default.
  88. var name string
  89. name, err = os.Hostname()
  90. if err != nil {
  91. name = tlsDefaultCommonName
  92. }
  93. cert, err = tlsutil.NewCertificate(locations[locHTTPSCertFile], locations[locHTTPSKeyFile], name, tlsRSABits)
  94. }
  95. if err != nil {
  96. return nil, err
  97. }
  98. tlsCfg := &tls.Config{
  99. Certificates: []tls.Certificate{cert},
  100. MinVersion: tls.VersionTLS10, // No SSLv3
  101. CipherSuites: []uint16{
  102. // No RC4
  103. tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
  104. tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
  105. tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
  106. tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
  107. tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
  108. tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
  109. tls.TLS_RSA_WITH_AES_128_CBC_SHA,
  110. tls.TLS_RSA_WITH_AES_256_CBC_SHA,
  111. tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
  112. tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
  113. },
  114. }
  115. rawListener, err := net.Listen("tcp", cfg.Address)
  116. if err != nil {
  117. return nil, err
  118. }
  119. listener := &tlsutil.DowngradingListener{rawListener, tlsCfg}
  120. return listener, nil
  121. }
  122. func (s *apiSvc) Serve() {
  123. s.stop = make(chan struct{})
  124. // The GET handlers
  125. getRestMux := http.NewServeMux()
  126. getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // device folder
  127. getRestMux.HandleFunc("/rest/db/file", s.getDBFile) // folder file
  128. getRestMux.HandleFunc("/rest/db/ignores", s.getDBIgnores) // folder
  129. getRestMux.HandleFunc("/rest/db/need", s.getDBNeed) // folder [perpage] [page]
  130. getRestMux.HandleFunc("/rest/db/status", s.getDBStatus) // folder
  131. getRestMux.HandleFunc("/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
  132. getRestMux.HandleFunc("/rest/events", s.getEvents) // since [limit]
  133. getRestMux.HandleFunc("/rest/stats/device", s.getDeviceStats) // -
  134. getRestMux.HandleFunc("/rest/stats/folder", s.getFolderStats) // -
  135. getRestMux.HandleFunc("/rest/svc/deviceid", s.getDeviceID) // id
  136. getRestMux.HandleFunc("/rest/svc/lang", s.getLang) // -
  137. getRestMux.HandleFunc("/rest/svc/report", s.getReport) // -
  138. getRestMux.HandleFunc("/rest/system/browse", s.getSystemBrowse) // current
  139. getRestMux.HandleFunc("/rest/system/config", s.getSystemConfig) // -
  140. getRestMux.HandleFunc("/rest/system/config/insync", s.getSystemConfigInsync) // -
  141. getRestMux.HandleFunc("/rest/system/connections", s.getSystemConnections) // -
  142. getRestMux.HandleFunc("/rest/system/discovery", s.getSystemDiscovery) // -
  143. getRestMux.HandleFunc("/rest/system/error", s.getSystemError) // -
  144. getRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
  145. getRestMux.HandleFunc("/rest/system/status", s.getSystemStatus) // -
  146. getRestMux.HandleFunc("/rest/system/upgrade", s.getSystemUpgrade) // -
  147. getRestMux.HandleFunc("/rest/system/version", s.getSystemVersion) // -
  148. getRestMux.HandleFunc("/rest/system/debug", s.getSystemDebug) // -
  149. getRestMux.HandleFunc("/rest/system/log", s.getSystemLog) // [since]
  150. getRestMux.HandleFunc("/rest/system/log.txt", s.getSystemLogTxt) // [since]
  151. // The POST handlers
  152. postRestMux := http.NewServeMux()
  153. postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
  154. postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder
  155. postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
  156. postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
  157. postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
  158. postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // <body>
  159. postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
  160. postRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
  161. postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset) // [folder]
  162. postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart) // -
  163. postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown) // -
  164. postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
  165. postRestMux.HandleFunc("/rest/system/pause", s.postSystemPause) // device
  166. postRestMux.HandleFunc("/rest/system/resume", s.postSystemResume) // device
  167. postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
  168. // Debug endpoints, not for general use
  169. getRestMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
  170. // A handler that splits requests between the two above and disables
  171. // caching
  172. restMux := noCacheMiddleware(getPostHandler(getRestMux, postRestMux))
  173. // The main routing handler
  174. mux := http.NewServeMux()
  175. mux.Handle("/rest/", restMux)
  176. mux.HandleFunc("/qr/", s.getQR)
  177. // Serve compiled in assets unless an asset directory was set (for development)
  178. mux.Handle("/", embeddedStatic{
  179. assetDir: s.assetDir,
  180. assets: auto.Assets(),
  181. })
  182. guiCfg := s.cfg.GUI()
  183. if guiAPIKey != "" {
  184. // Override from the environment
  185. guiCfg.APIKey = guiAPIKey
  186. }
  187. // Wrap everything in CSRF protection. The /rest prefix should be
  188. // protected, other requests will grant cookies.
  189. handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg.APIKey, mux)
  190. // Add our version and ID as a header to responses
  191. handler = withDetailsMiddleware(s.id, handler)
  192. // Wrap everything in basic auth, if user/password is set.
  193. if len(guiCfg.User) > 0 && len(guiCfg.Password) > 0 {
  194. handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, handler)
  195. }
  196. // Redirect to HTTPS if we are supposed to
  197. if guiCfg.UseTLS {
  198. handler = redirectToHTTPSMiddleware(handler)
  199. }
  200. handler = debugMiddleware(handler)
  201. srv := http.Server{
  202. Handler: handler,
  203. ReadTimeout: 10 * time.Second,
  204. }
  205. s.fss = newFolderSummarySvc(s.cfg, s.model)
  206. defer s.fss.Stop()
  207. s.fss.ServeBackground()
  208. l.Infoln("API listening on", s.listener.Addr())
  209. err := srv.Serve(s.listener)
  210. // The return could be due to an intentional close. Wait for the stop
  211. // signal before returning. IF there is no stop signal within a second, we
  212. // assume it was unintentional and log the error before retrying.
  213. select {
  214. case <-s.stop:
  215. case <-time.After(time.Second):
  216. l.Warnln("API:", err)
  217. }
  218. }
  219. func (s *apiSvc) Stop() {
  220. close(s.stop)
  221. s.listener.Close()
  222. }
  223. func (s *apiSvc) String() string {
  224. return fmt.Sprintf("apiSvc@%p", s)
  225. }
  226. func (s *apiSvc) VerifyConfiguration(from, to config.Configuration) error {
  227. return nil
  228. }
  229. func (s *apiSvc) CommitConfiguration(from, to config.Configuration) bool {
  230. if to.GUI == from.GUI {
  231. return true
  232. }
  233. // Order here is important. We must close the listener to stop Serve(). We
  234. // must create a new listener before Serve() starts again. We can't create
  235. // a new listener on the same port before the previous listener is closed.
  236. // To assist in this little dance the Serve() method will wait for a
  237. // signal on the stop channel after the listener has closed.
  238. s.listener.Close()
  239. var err error
  240. s.listener, err = s.getListener(to.GUI)
  241. if err != nil {
  242. // Ideally this should be a verification error, but we check it by
  243. // creating a new listener which requires shutting down the previous
  244. // one first, which is too destructive for the VerifyConfiguration
  245. // method.
  246. return false
  247. }
  248. close(s.stop)
  249. return true
  250. }
  251. func getPostHandler(get, post http.Handler) http.Handler {
  252. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  253. switch r.Method {
  254. case "GET":
  255. get.ServeHTTP(w, r)
  256. case "POST":
  257. post.ServeHTTP(w, r)
  258. default:
  259. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  260. }
  261. })
  262. }
  263. func debugMiddleware(h http.Handler) http.Handler {
  264. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  265. t0 := time.Now()
  266. h.ServeHTTP(w, r)
  267. ms := 1000 * time.Since(t0).Seconds()
  268. // The variable `w` is most likely a *http.response, which we can't do
  269. // much with since it's a non exported type. We can however peek into
  270. // it with reflection to get at the status code and number of bytes
  271. // written.
  272. var status, written int64
  273. if rw := reflect.Indirect(reflect.ValueOf(w)); rw.IsValid() && rw.Kind() == reflect.Struct {
  274. if rf := rw.FieldByName("status"); rf.IsValid() && rf.Kind() == reflect.Int {
  275. status = rf.Int()
  276. }
  277. if rf := rw.FieldByName("written"); rf.IsValid() && rf.Kind() == reflect.Int64 {
  278. written = rf.Int()
  279. }
  280. }
  281. l.Debugf("http: %s %q: status %d, %d bytes in %.02f ms", r.Method, r.URL.String(), status, written, ms)
  282. })
  283. }
  284. func redirectToHTTPSMiddleware(h http.Handler) http.Handler {
  285. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  286. // Add a generous access-control-allow-origin header since we may be
  287. // redirecting REST requests over protocols
  288. w.Header().Add("Access-Control-Allow-Origin", "*")
  289. if r.TLS == nil {
  290. // Redirect HTTP requests to HTTPS
  291. r.URL.Host = r.Host
  292. r.URL.Scheme = "https"
  293. http.Redirect(w, r, r.URL.String(), http.StatusFound)
  294. } else {
  295. h.ServeHTTP(w, r)
  296. }
  297. })
  298. }
  299. func noCacheMiddleware(h http.Handler) http.Handler {
  300. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  301. w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store")
  302. w.Header().Set("Expires", time.Now().UTC().Format(http.TimeFormat))
  303. w.Header().Set("Pragma", "no-cache")
  304. h.ServeHTTP(w, r)
  305. })
  306. }
  307. func withDetailsMiddleware(id protocol.DeviceID, h http.Handler) http.Handler {
  308. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  309. w.Header().Set("X-Syncthing-Version", Version)
  310. w.Header().Set("X-Syncthing-ID", id.String())
  311. h.ServeHTTP(w, r)
  312. })
  313. }
  314. func (s *apiSvc) restPing(w http.ResponseWriter, r *http.Request) {
  315. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  316. json.NewEncoder(w).Encode(map[string]string{
  317. "ping": "pong",
  318. })
  319. }
  320. func (s *apiSvc) getSystemVersion(w http.ResponseWriter, r *http.Request) {
  321. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  322. json.NewEncoder(w).Encode(map[string]string{
  323. "version": Version,
  324. "codename": Codename,
  325. "longVersion": LongVersion,
  326. "os": runtime.GOOS,
  327. "arch": runtime.GOARCH,
  328. })
  329. }
  330. func (s *apiSvc) getSystemDebug(w http.ResponseWriter, r *http.Request) {
  331. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  332. names := l.Facilities()
  333. enabled := l.FacilityDebugging()
  334. sort.Strings(enabled)
  335. json.NewEncoder(w).Encode(map[string]interface{}{
  336. "facilities": names,
  337. "enabled": enabled,
  338. })
  339. }
  340. func (s *apiSvc) postSystemDebug(w http.ResponseWriter, r *http.Request) {
  341. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  342. q := r.URL.Query()
  343. for _, f := range strings.Split(q.Get("enable"), ",") {
  344. if f == "" {
  345. continue
  346. }
  347. l.SetDebug(f, true)
  348. l.Infof("Enabled debug data for %q", f)
  349. }
  350. for _, f := range strings.Split(q.Get("disable"), ",") {
  351. if f == "" {
  352. continue
  353. }
  354. l.SetDebug(f, false)
  355. l.Infof("Disabled debug data for %q", f)
  356. }
  357. }
  358. func (s *apiSvc) getDBBrowse(w http.ResponseWriter, r *http.Request) {
  359. qs := r.URL.Query()
  360. folder := qs.Get("folder")
  361. prefix := qs.Get("prefix")
  362. dirsonly := qs.Get("dirsonly") != ""
  363. levels, err := strconv.Atoi(qs.Get("levels"))
  364. if err != nil {
  365. levels = -1
  366. }
  367. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  368. tree := s.model.GlobalDirectoryTree(folder, prefix, levels, dirsonly)
  369. json.NewEncoder(w).Encode(tree)
  370. }
  371. func (s *apiSvc) getDBCompletion(w http.ResponseWriter, r *http.Request) {
  372. var qs = r.URL.Query()
  373. var folder = qs.Get("folder")
  374. var deviceStr = qs.Get("device")
  375. device, err := protocol.DeviceIDFromString(deviceStr)
  376. if err != nil {
  377. http.Error(w, err.Error(), 500)
  378. return
  379. }
  380. res := map[string]float64{
  381. "completion": s.model.Completion(device, folder),
  382. }
  383. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  384. json.NewEncoder(w).Encode(res)
  385. }
  386. func (s *apiSvc) getDBStatus(w http.ResponseWriter, r *http.Request) {
  387. qs := r.URL.Query()
  388. folder := qs.Get("folder")
  389. res := folderSummary(s.cfg, s.model, folder)
  390. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  391. json.NewEncoder(w).Encode(res)
  392. }
  393. func folderSummary(cfg *config.Wrapper, m *model.Model, folder string) map[string]interface{} {
  394. var res = make(map[string]interface{})
  395. res["invalid"] = cfg.Folders()[folder].Invalid
  396. globalFiles, globalDeleted, globalBytes := m.GlobalSize(folder)
  397. res["globalFiles"], res["globalDeleted"], res["globalBytes"] = globalFiles, globalDeleted, globalBytes
  398. localFiles, localDeleted, localBytes := m.LocalSize(folder)
  399. res["localFiles"], res["localDeleted"], res["localBytes"] = localFiles, localDeleted, localBytes
  400. needFiles, needBytes := m.NeedSize(folder)
  401. res["needFiles"], res["needBytes"] = needFiles, needBytes
  402. res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes
  403. var err error
  404. res["state"], res["stateChanged"], err = m.State(folder)
  405. if err != nil {
  406. res["error"] = err.Error()
  407. }
  408. lv, _ := m.CurrentLocalVersion(folder)
  409. rv, _ := m.RemoteLocalVersion(folder)
  410. res["version"] = lv + rv
  411. ignorePatterns, _, _ := m.GetIgnores(folder)
  412. res["ignorePatterns"] = false
  413. for _, line := range ignorePatterns {
  414. if len(line) > 0 && !strings.HasPrefix(line, "//") {
  415. res["ignorePatterns"] = true
  416. break
  417. }
  418. }
  419. return res
  420. }
  421. func (s *apiSvc) postDBOverride(w http.ResponseWriter, r *http.Request) {
  422. var qs = r.URL.Query()
  423. var folder = qs.Get("folder")
  424. go s.model.Override(folder)
  425. }
  426. func (s *apiSvc) getDBNeed(w http.ResponseWriter, r *http.Request) {
  427. qs := r.URL.Query()
  428. folder := qs.Get("folder")
  429. page, err := strconv.Atoi(qs.Get("page"))
  430. if err != nil || page < 1 {
  431. page = 1
  432. }
  433. perpage, err := strconv.Atoi(qs.Get("perpage"))
  434. if err != nil || perpage < 1 {
  435. perpage = 1 << 16
  436. }
  437. progress, queued, rest, total := s.model.NeedFolderFiles(folder, page, perpage)
  438. // Convert the struct to a more loose structure, and inject the size.
  439. output := map[string]interface{}{
  440. "progress": s.toNeedSlice(progress),
  441. "queued": s.toNeedSlice(queued),
  442. "rest": s.toNeedSlice(rest),
  443. "total": total,
  444. "page": page,
  445. "perpage": perpage,
  446. }
  447. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  448. json.NewEncoder(w).Encode(output)
  449. }
  450. func (s *apiSvc) getSystemConnections(w http.ResponseWriter, r *http.Request) {
  451. var res = s.model.ConnectionStats()
  452. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  453. json.NewEncoder(w).Encode(res)
  454. }
  455. func (s *apiSvc) getDeviceStats(w http.ResponseWriter, r *http.Request) {
  456. var res = s.model.DeviceStatistics()
  457. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  458. json.NewEncoder(w).Encode(res)
  459. }
  460. func (s *apiSvc) getFolderStats(w http.ResponseWriter, r *http.Request) {
  461. var res = s.model.FolderStatistics()
  462. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  463. json.NewEncoder(w).Encode(res)
  464. }
  465. func (s *apiSvc) getDBFile(w http.ResponseWriter, r *http.Request) {
  466. qs := r.URL.Query()
  467. folder := qs.Get("folder")
  468. file := qs.Get("file")
  469. gf, _ := s.model.CurrentGlobalFile(folder, file)
  470. lf, _ := s.model.CurrentFolderFile(folder, file)
  471. av := s.model.Availability(folder, file)
  472. json.NewEncoder(w).Encode(map[string]interface{}{
  473. "global": jsonFileInfo(gf),
  474. "local": jsonFileInfo(lf),
  475. "availability": av,
  476. })
  477. }
  478. func (s *apiSvc) getSystemConfig(w http.ResponseWriter, r *http.Request) {
  479. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  480. json.NewEncoder(w).Encode(s.cfg.Raw())
  481. }
  482. func (s *apiSvc) postSystemConfig(w http.ResponseWriter, r *http.Request) {
  483. s.systemConfigMut.Lock()
  484. defer s.systemConfigMut.Unlock()
  485. var to config.Configuration
  486. err := json.NewDecoder(r.Body).Decode(&to)
  487. if err != nil {
  488. l.Warnln("decoding posted config:", err)
  489. http.Error(w, err.Error(), 500)
  490. return
  491. }
  492. if to.GUI.Password != s.cfg.GUI().Password {
  493. if to.GUI.Password != "" {
  494. hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0)
  495. if err != nil {
  496. l.Warnln("bcrypting password:", err)
  497. http.Error(w, err.Error(), 500)
  498. return
  499. }
  500. to.GUI.Password = string(hash)
  501. }
  502. }
  503. // Fixup usage reporting settings
  504. if curAcc := s.cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
  505. // UR was enabled
  506. to.Options.URAccepted = usageReportVersion
  507. to.Options.URUniqueID = randomString(8)
  508. } else if to.Options.URAccepted < curAcc {
  509. // UR was disabled
  510. to.Options.URAccepted = -1
  511. to.Options.URUniqueID = ""
  512. }
  513. // Activate and save
  514. resp := s.cfg.Replace(to)
  515. configInSync = !resp.RequiresRestart
  516. s.cfg.Save()
  517. }
  518. func (s *apiSvc) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
  519. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  520. json.NewEncoder(w).Encode(map[string]bool{"configInSync": configInSync})
  521. }
  522. func (s *apiSvc) postSystemRestart(w http.ResponseWriter, r *http.Request) {
  523. s.flushResponse(`{"ok": "restarting"}`, w)
  524. go restart()
  525. }
  526. func (s *apiSvc) postSystemReset(w http.ResponseWriter, r *http.Request) {
  527. var qs = r.URL.Query()
  528. folder := qs.Get("folder")
  529. if len(folder) > 0 {
  530. if _, ok := s.cfg.Folders()[folder]; !ok {
  531. http.Error(w, "Invalid folder ID", 500)
  532. return
  533. }
  534. }
  535. if len(folder) == 0 {
  536. // Reset all folders.
  537. for folder := range s.cfg.Folders() {
  538. s.model.ResetFolder(folder)
  539. }
  540. s.flushResponse(`{"ok": "resetting database"}`, w)
  541. } else {
  542. // Reset a specific folder, assuming it's supposed to exist.
  543. s.model.ResetFolder(folder)
  544. s.flushResponse(`{"ok": "resetting folder `+folder+`"}`, w)
  545. }
  546. go restart()
  547. }
  548. func (s *apiSvc) postSystemShutdown(w http.ResponseWriter, r *http.Request) {
  549. s.flushResponse(`{"ok": "shutting down"}`, w)
  550. go shutdown()
  551. }
  552. func (s *apiSvc) flushResponse(resp string, w http.ResponseWriter) {
  553. w.Write([]byte(resp + "\n"))
  554. f := w.(http.Flusher)
  555. f.Flush()
  556. }
  557. var cpuUsagePercent [10]float64 // The last ten seconds
  558. var cpuUsageLock = sync.NewRWMutex()
  559. func (s *apiSvc) getSystemStatus(w http.ResponseWriter, r *http.Request) {
  560. var m runtime.MemStats
  561. runtime.ReadMemStats(&m)
  562. tilde, _ := osutil.ExpandTilde("~")
  563. res := make(map[string]interface{})
  564. res["myID"] = myID.String()
  565. res["goroutines"] = runtime.NumGoroutine()
  566. res["alloc"] = m.Alloc
  567. res["sys"] = m.Sys - m.HeapReleased
  568. res["tilde"] = tilde
  569. if s.cfg.Options().LocalAnnEnabled || s.cfg.Options().GlobalAnnEnabled {
  570. res["discoveryEnabled"] = true
  571. discoErrors := make(map[string]string)
  572. discoMethods := 0
  573. for disco, err := range s.discoverer.ChildErrors() {
  574. discoMethods++
  575. if err != nil {
  576. discoErrors[disco] = err.Error()
  577. }
  578. }
  579. res["discoveryMethods"] = discoMethods
  580. res["discoveryErrors"] = discoErrors
  581. }
  582. if s.relaySvc != nil {
  583. res["relaysEnabled"] = true
  584. relayClientStatus := make(map[string]bool)
  585. relayClientLatency := make(map[string]int)
  586. for _, relay := range s.relaySvc.Relays() {
  587. latency, ok := s.relaySvc.RelayStatus(relay)
  588. relayClientStatus[relay] = ok
  589. relayClientLatency[relay] = int(latency / time.Millisecond)
  590. }
  591. res["relayClientStatus"] = relayClientStatus
  592. res["relayClientLatency"] = relayClientLatency
  593. }
  594. cpuUsageLock.RLock()
  595. var cpusum float64
  596. for _, p := range cpuUsagePercent {
  597. cpusum += p
  598. }
  599. cpuUsageLock.RUnlock()
  600. res["cpuPercent"] = cpusum / float64(len(cpuUsagePercent)) / float64(runtime.NumCPU())
  601. res["pathSeparator"] = string(filepath.Separator)
  602. res["uptime"] = int(time.Since(startTime).Seconds())
  603. res["startTime"] = startTime
  604. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  605. json.NewEncoder(w).Encode(res)
  606. }
  607. func (s *apiSvc) getSystemError(w http.ResponseWriter, r *http.Request) {
  608. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  609. json.NewEncoder(w).Encode(map[string][]logger.Line{
  610. "errors": s.guiErrors.Since(time.Time{}),
  611. })
  612. }
  613. func (s *apiSvc) postSystemError(w http.ResponseWriter, r *http.Request) {
  614. bs, _ := ioutil.ReadAll(r.Body)
  615. r.Body.Close()
  616. l.Warnln(string(bs))
  617. }
  618. func (s *apiSvc) postSystemErrorClear(w http.ResponseWriter, r *http.Request) {
  619. s.guiErrors.Clear()
  620. }
  621. func (s *apiSvc) getSystemLog(w http.ResponseWriter, r *http.Request) {
  622. q := r.URL.Query()
  623. since, err := time.Parse(time.RFC3339, q.Get("since"))
  624. l.Debugln(err)
  625. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  626. json.NewEncoder(w).Encode(map[string][]logger.Line{
  627. "messages": s.systemLog.Since(since),
  628. })
  629. }
  630. func (s *apiSvc) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
  631. q := r.URL.Query()
  632. since, err := time.Parse(time.RFC3339, q.Get("since"))
  633. l.Debugln(err)
  634. w.Header().Set("Content-Type", "text/plain; charset=utf-8")
  635. for _, line := range s.systemLog.Since(since) {
  636. fmt.Fprintf(w, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
  637. }
  638. }
  639. func (s *apiSvc) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
  640. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  641. devices := make(map[string]discover.CacheEntry)
  642. if s.discoverer != nil {
  643. // Device ids can't be marshalled as keys so we need to manually
  644. // rebuild this map using strings. Discoverer may be nil if discovery
  645. // has not started yet.
  646. for device, entry := range s.discoverer.Cache() {
  647. devices[device.String()] = entry
  648. }
  649. }
  650. json.NewEncoder(w).Encode(devices)
  651. }
  652. func (s *apiSvc) getReport(w http.ResponseWriter, r *http.Request) {
  653. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  654. json.NewEncoder(w).Encode(reportData(s.cfg, s.model))
  655. }
  656. func (s *apiSvc) getDBIgnores(w http.ResponseWriter, r *http.Request) {
  657. qs := r.URL.Query()
  658. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  659. ignores, patterns, err := s.model.GetIgnores(qs.Get("folder"))
  660. if err != nil {
  661. http.Error(w, err.Error(), 500)
  662. return
  663. }
  664. json.NewEncoder(w).Encode(map[string][]string{
  665. "ignore": ignores,
  666. "patterns": patterns,
  667. })
  668. }
  669. func (s *apiSvc) postDBIgnores(w http.ResponseWriter, r *http.Request) {
  670. qs := r.URL.Query()
  671. var data map[string][]string
  672. err := json.NewDecoder(r.Body).Decode(&data)
  673. r.Body.Close()
  674. if err != nil {
  675. http.Error(w, err.Error(), 500)
  676. return
  677. }
  678. err = s.model.SetIgnores(qs.Get("folder"), data["ignore"])
  679. if err != nil {
  680. http.Error(w, err.Error(), 500)
  681. return
  682. }
  683. s.getDBIgnores(w, r)
  684. }
  685. func (s *apiSvc) getEvents(w http.ResponseWriter, r *http.Request) {
  686. qs := r.URL.Query()
  687. sinceStr := qs.Get("since")
  688. limitStr := qs.Get("limit")
  689. since, _ := strconv.Atoi(sinceStr)
  690. limit, _ := strconv.Atoi(limitStr)
  691. s.fss.gotEventRequest()
  692. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  693. // Flush before blocking, to indicate that we've received the request
  694. // and that it should not be retried.
  695. f := w.(http.Flusher)
  696. f.Flush()
  697. evs := s.eventSub.Since(since, nil)
  698. if 0 < limit && limit < len(evs) {
  699. evs = evs[len(evs)-limit:]
  700. }
  701. json.NewEncoder(w).Encode(evs)
  702. }
  703. func (s *apiSvc) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
  704. if noUpgrade {
  705. http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
  706. return
  707. }
  708. rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
  709. if err != nil {
  710. http.Error(w, err.Error(), 500)
  711. return
  712. }
  713. res := make(map[string]interface{})
  714. res["running"] = Version
  715. res["latest"] = rel.Tag
  716. res["newer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.Newer
  717. res["majorNewer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.MajorNewer
  718. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  719. json.NewEncoder(w).Encode(res)
  720. }
  721. func (s *apiSvc) getDeviceID(w http.ResponseWriter, r *http.Request) {
  722. qs := r.URL.Query()
  723. idStr := qs.Get("id")
  724. id, err := protocol.DeviceIDFromString(idStr)
  725. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  726. if err == nil {
  727. json.NewEncoder(w).Encode(map[string]string{
  728. "id": id.String(),
  729. })
  730. } else {
  731. json.NewEncoder(w).Encode(map[string]string{
  732. "error": err.Error(),
  733. })
  734. }
  735. }
  736. func (s *apiSvc) getLang(w http.ResponseWriter, r *http.Request) {
  737. lang := r.Header.Get("Accept-Language")
  738. var langs []string
  739. for _, l := range strings.Split(lang, ",") {
  740. parts := strings.SplitN(l, ";", 2)
  741. langs = append(langs, strings.ToLower(strings.TrimSpace(parts[0])))
  742. }
  743. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  744. json.NewEncoder(w).Encode(langs)
  745. }
  746. func (s *apiSvc) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
  747. rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
  748. if err != nil {
  749. l.Warnln("getting latest release:", err)
  750. http.Error(w, err.Error(), 500)
  751. return
  752. }
  753. if upgrade.CompareVersions(rel.Tag, Version) > upgrade.Equal {
  754. err = upgrade.To(rel)
  755. if err != nil {
  756. l.Warnln("upgrading:", err)
  757. http.Error(w, err.Error(), 500)
  758. return
  759. }
  760. s.flushResponse(`{"ok": "restarting"}`, w)
  761. l.Infoln("Upgrading")
  762. stop <- exitUpgrading
  763. }
  764. }
  765. func (s *apiSvc) postSystemPause(w http.ResponseWriter, r *http.Request) {
  766. var qs = r.URL.Query()
  767. var deviceStr = qs.Get("device")
  768. device, err := protocol.DeviceIDFromString(deviceStr)
  769. if err != nil {
  770. http.Error(w, err.Error(), 500)
  771. return
  772. }
  773. s.model.PauseDevice(device)
  774. }
  775. func (s *apiSvc) postSystemResume(w http.ResponseWriter, r *http.Request) {
  776. var qs = r.URL.Query()
  777. var deviceStr = qs.Get("device")
  778. device, err := protocol.DeviceIDFromString(deviceStr)
  779. if err != nil {
  780. http.Error(w, err.Error(), 500)
  781. return
  782. }
  783. s.model.ResumeDevice(device)
  784. }
  785. func (s *apiSvc) postDBScan(w http.ResponseWriter, r *http.Request) {
  786. qs := r.URL.Query()
  787. folder := qs.Get("folder")
  788. if folder != "" {
  789. nextStr := qs.Get("next")
  790. next, err := strconv.Atoi(nextStr)
  791. if err == nil {
  792. s.model.DelayScan(folder, time.Duration(next)*time.Second)
  793. }
  794. subs := qs["sub"]
  795. err = s.model.ScanFolderSubs(folder, subs)
  796. if err != nil {
  797. http.Error(w, err.Error(), 500)
  798. return
  799. }
  800. } else {
  801. errors := s.model.ScanFolders()
  802. if len(errors) > 0 {
  803. http.Error(w, "Error scanning folders", 500)
  804. json.NewEncoder(w).Encode(errors)
  805. return
  806. }
  807. }
  808. }
  809. func (s *apiSvc) postDBPrio(w http.ResponseWriter, r *http.Request) {
  810. qs := r.URL.Query()
  811. folder := qs.Get("folder")
  812. file := qs.Get("file")
  813. s.model.BringToFront(folder, file)
  814. s.getDBNeed(w, r)
  815. }
  816. func (s *apiSvc) getQR(w http.ResponseWriter, r *http.Request) {
  817. var qs = r.URL.Query()
  818. var text = qs.Get("text")
  819. code, err := qr.Encode(text, qr.M)
  820. if err != nil {
  821. http.Error(w, "Invalid", 500)
  822. return
  823. }
  824. w.Header().Set("Content-Type", "image/png")
  825. w.Write(code.PNG())
  826. }
  827. func (s *apiSvc) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
  828. tot := map[string]float64{}
  829. count := map[string]float64{}
  830. for _, folder := range s.cfg.Folders() {
  831. for _, device := range folder.DeviceIDs() {
  832. deviceStr := device.String()
  833. if s.model.ConnectedTo(device) {
  834. tot[deviceStr] += s.model.Completion(device, folder.ID)
  835. } else {
  836. tot[deviceStr] = 0
  837. }
  838. count[deviceStr]++
  839. }
  840. }
  841. comp := map[string]int{}
  842. for device := range tot {
  843. comp[device] = int(tot[device] / count[device])
  844. }
  845. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  846. json.NewEncoder(w).Encode(comp)
  847. }
  848. func (s *apiSvc) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
  849. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  850. qs := r.URL.Query()
  851. current := qs.Get("current")
  852. search, _ := osutil.ExpandTilde(current)
  853. pathSeparator := string(os.PathSeparator)
  854. if strings.HasSuffix(current, pathSeparator) && !strings.HasSuffix(search, pathSeparator) {
  855. search = search + pathSeparator
  856. }
  857. subdirectories, _ := osutil.Glob(search + "*")
  858. ret := make([]string, 0, 10)
  859. for _, subdirectory := range subdirectories {
  860. info, err := os.Stat(subdirectory)
  861. if err == nil && info.IsDir() {
  862. ret = append(ret, subdirectory+pathSeparator)
  863. if len(ret) > 9 {
  864. break
  865. }
  866. }
  867. }
  868. json.NewEncoder(w).Encode(ret)
  869. }
  870. type embeddedStatic struct {
  871. assetDir string
  872. assets map[string][]byte
  873. }
  874. func (s embeddedStatic) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  875. file := r.URL.Path
  876. if file[0] == '/' {
  877. file = file[1:]
  878. }
  879. if len(file) == 0 {
  880. file = "index.html"
  881. }
  882. if s.assetDir != "" {
  883. p := filepath.Join(s.assetDir, filepath.FromSlash(file))
  884. _, err := os.Stat(p)
  885. if err == nil {
  886. http.ServeFile(w, r, p)
  887. return
  888. }
  889. }
  890. bs, ok := s.assets[file]
  891. if !ok {
  892. http.NotFound(w, r)
  893. return
  894. }
  895. if r.Header.Get("If-Modified-Since") == auto.AssetsBuildDate {
  896. w.WriteHeader(http.StatusNotModified)
  897. return
  898. }
  899. mtype := s.mimeTypeForFile(file)
  900. if len(mtype) != 0 {
  901. w.Header().Set("Content-Type", mtype)
  902. }
  903. if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
  904. w.Header().Set("Content-Encoding", "gzip")
  905. } else {
  906. // ungzip if browser not send gzip accepted header
  907. var gr *gzip.Reader
  908. gr, _ = gzip.NewReader(bytes.NewReader(bs))
  909. bs, _ = ioutil.ReadAll(gr)
  910. gr.Close()
  911. }
  912. w.Header().Set("Content-Length", fmt.Sprintf("%d", len(bs)))
  913. w.Header().Set("Last-Modified", auto.AssetsBuildDate)
  914. w.Header().Set("Cache-Control", "public")
  915. w.Write(bs)
  916. }
  917. func (s embeddedStatic) mimeTypeForFile(file string) string {
  918. // We use a built in table of the common types since the system
  919. // TypeByExtension might be unreliable. But if we don't know, we delegate
  920. // to the system.
  921. ext := filepath.Ext(file)
  922. switch ext {
  923. case ".htm", ".html":
  924. return "text/html"
  925. case ".css":
  926. return "text/css"
  927. case ".js":
  928. return "application/javascript"
  929. case ".json":
  930. return "application/json"
  931. case ".png":
  932. return "image/png"
  933. case ".ttf":
  934. return "application/x-font-ttf"
  935. case ".woff":
  936. return "application/x-font-woff"
  937. case ".svg":
  938. return "image/svg+xml"
  939. default:
  940. return mime.TypeByExtension(ext)
  941. }
  942. }
  943. func (s *apiSvc) toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
  944. res := make([]jsonDBFileInfo, len(fs))
  945. for i, f := range fs {
  946. res[i] = jsonDBFileInfo(f)
  947. }
  948. return res
  949. }
  950. // Type wrappers for nice JSON serialization
  951. type jsonFileInfo protocol.FileInfo
  952. func (f jsonFileInfo) MarshalJSON() ([]byte, error) {
  953. return json.Marshal(map[string]interface{}{
  954. "name": f.Name,
  955. "size": protocol.FileInfo(f).Size(),
  956. "flags": fmt.Sprintf("%#o", f.Flags),
  957. "modified": time.Unix(f.Modified, 0),
  958. "localVersion": f.LocalVersion,
  959. "numBlocks": len(f.Blocks),
  960. "version": jsonVersionVector(f.Version),
  961. })
  962. }
  963. type jsonDBFileInfo db.FileInfoTruncated
  964. func (f jsonDBFileInfo) MarshalJSON() ([]byte, error) {
  965. return json.Marshal(map[string]interface{}{
  966. "name": f.Name,
  967. "size": db.FileInfoTruncated(f).Size(),
  968. "flags": fmt.Sprintf("%#o", f.Flags),
  969. "modified": time.Unix(f.Modified, 0),
  970. "localVersion": f.LocalVersion,
  971. "version": jsonVersionVector(f.Version),
  972. })
  973. }
  974. type jsonVersionVector protocol.Vector
  975. func (v jsonVersionVector) MarshalJSON() ([]byte, error) {
  976. res := make([]string, len(v))
  977. for i, c := range v {
  978. res[i] = fmt.Sprintf("%d:%d", c.ID, c.Value)
  979. }
  980. return json.Marshal(res)
  981. }