|
@@ -31,10 +31,10 @@ import (
|
|
"strings"
|
|
"strings"
|
|
"time"
|
|
"time"
|
|
|
|
|
|
|
|
+ "github.com/julienschmidt/httprouter"
|
|
metrics "github.com/rcrowley/go-metrics"
|
|
metrics "github.com/rcrowley/go-metrics"
|
|
"github.com/thejerf/suture"
|
|
"github.com/thejerf/suture"
|
|
"github.com/vitrun/qart/qr"
|
|
"github.com/vitrun/qart/qr"
|
|
- "golang.org/x/crypto/bcrypt"
|
|
|
|
|
|
|
|
"github.com/syncthing/syncthing/lib/build"
|
|
"github.com/syncthing/syncthing/lib/build"
|
|
"github.com/syncthing/syncthing/lib/config"
|
|
"github.com/syncthing/syncthing/lib/config"
|
|
@@ -81,7 +81,6 @@ type service struct {
|
|
connectionsService connections.Service
|
|
connectionsService connections.Service
|
|
fss model.FolderSummaryService
|
|
fss model.FolderSummaryService
|
|
urService *ur.Service
|
|
urService *ur.Service
|
|
- systemConfigMut sync.Mutex // serializes posts to /rest/system/config
|
|
|
|
contr Controller
|
|
contr Controller
|
|
noUpgrade bool
|
|
noUpgrade bool
|
|
tlsDefaultCommonName string
|
|
tlsDefaultCommonName string
|
|
@@ -123,7 +122,6 @@ func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonNam
|
|
connectionsService: connectionsService,
|
|
connectionsService: connectionsService,
|
|
fss: fss,
|
|
fss: fss,
|
|
urService: urService,
|
|
urService: urService,
|
|
- systemConfigMut: sync.NewMutex(),
|
|
|
|
guiErrors: errors,
|
|
guiErrors: errors,
|
|
systemLog: systemLog,
|
|
systemLog: systemLog,
|
|
contr: contr,
|
|
contr: contr,
|
|
@@ -243,60 +241,80 @@ func (s *service) serve(ctx context.Context) {
|
|
s.cfg.Subscribe(s)
|
|
s.cfg.Subscribe(s)
|
|
defer s.cfg.Unsubscribe(s)
|
|
defer s.cfg.Unsubscribe(s)
|
|
|
|
|
|
|
|
+ restMux := httprouter.New()
|
|
|
|
+
|
|
// The GET handlers
|
|
// The GET handlers
|
|
- getRestMux := http.NewServeMux()
|
|
|
|
- getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // [device] [folder]
|
|
|
|
- getRestMux.HandleFunc("/rest/db/file", s.getDBFile) // folder file
|
|
|
|
- getRestMux.HandleFunc("/rest/db/ignores", s.getDBIgnores) // folder
|
|
|
|
- getRestMux.HandleFunc("/rest/db/need", s.getDBNeed) // folder [perpage] [page]
|
|
|
|
- getRestMux.HandleFunc("/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page]
|
|
|
|
- getRestMux.HandleFunc("/rest/db/localchanged", s.getDBLocalChanged) // folder
|
|
|
|
- getRestMux.HandleFunc("/rest/db/status", s.getDBStatus) // folder
|
|
|
|
- getRestMux.HandleFunc("/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
|
|
|
|
- getRestMux.HandleFunc("/rest/folder/versions", s.getFolderVersions) // folder
|
|
|
|
- getRestMux.HandleFunc("/rest/folder/errors", s.getFolderErrors) // folder
|
|
|
|
- getRestMux.HandleFunc("/rest/folder/pullerrors", s.getFolderErrors) // folder (deprecated)
|
|
|
|
- getRestMux.HandleFunc("/rest/events", s.getIndexEvents) // [since] [limit] [timeout] [events]
|
|
|
|
- getRestMux.HandleFunc("/rest/events/disk", s.getDiskEvents) // [since] [limit] [timeout]
|
|
|
|
- getRestMux.HandleFunc("/rest/stats/device", s.getDeviceStats) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/stats/folder", s.getFolderStats) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/svc/deviceid", s.getDeviceID) // id
|
|
|
|
- getRestMux.HandleFunc("/rest/svc/lang", s.getLang) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/svc/report", s.getReport) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/svc/random/string", s.getRandomString) // [length]
|
|
|
|
- getRestMux.HandleFunc("/rest/system/browse", s.getSystemBrowse) // current
|
|
|
|
- getRestMux.HandleFunc("/rest/system/config", s.getSystemConfig) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/system/config/insync", s.getSystemConfigInsync) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/system/connections", s.getSystemConnections) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/system/discovery", s.getSystemDiscovery) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/system/error", s.getSystemError) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/system/status", s.getSystemStatus) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/system/upgrade", s.getSystemUpgrade) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/system/version", s.getSystemVersion) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/system/debug", s.getSystemDebug) // -
|
|
|
|
- getRestMux.HandleFunc("/rest/system/log", s.getSystemLog) // [since]
|
|
|
|
- getRestMux.HandleFunc("/rest/system/log.txt", s.getSystemLogTxt) // [since]
|
|
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/db/completion", s.getDBCompletion) // [device] [folder]
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/db/file", s.getDBFile) // folder file
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/db/ignores", s.getDBIgnores) // folder
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/db/need", s.getDBNeed) // folder [perpage] [page]
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page]
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/db/localchanged", s.getDBLocalChanged) // folder
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/db/status", s.getDBStatus) // folder
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/folder/versions", s.getFolderVersions) // folder
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/folder/errors", s.getFolderErrors) // folder
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/folder/pullerrors", s.getFolderErrors) // folder (deprecated)
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/events", s.getIndexEvents) // [since] [limit] [timeout] [events]
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/events/disk", s.getDiskEvents) // [since] [limit] [timeout]
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/stats/device", s.getDeviceStats) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/stats/folder", s.getFolderStats) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/svc/deviceid", s.getDeviceID) // id
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/svc/lang", s.getLang) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/svc/report", s.getReport) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/svc/random/string", s.getRandomString) // [length]
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/browse", s.getSystemBrowse) // current
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/connections", s.getSystemConnections) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/discovery", s.getSystemDiscovery) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/error", s.getSystemError) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/ping", s.restPing) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/status", s.getSystemStatus) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/upgrade", s.getSystemUpgrade) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/version", s.getSystemVersion) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/debug", s.getSystemDebug) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/log", s.getSystemLog) // [since]
|
|
|
|
+ restMux.HandlerFunc(http.MethodGet, "/rest/system/log.txt", s.getSystemLogTxt) // [since]
|
|
|
|
|
|
// The POST handlers
|
|
// The POST handlers
|
|
- postRestMux := http.NewServeMux()
|
|
|
|
- postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
|
|
|
|
- postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder
|
|
|
|
- postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
|
|
|
|
- postRestMux.HandleFunc("/rest/db/revert", s.postDBRevert) // folder
|
|
|
|
- postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
|
|
|
|
- postRestMux.HandleFunc("/rest/folder/versions", s.postFolderVersionsRestore) // folder <body>
|
|
|
|
- postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
|
|
|
|
- postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // <body>
|
|
|
|
- postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
|
|
|
|
- postRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
|
|
|
|
- postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset) // [folder]
|
|
|
|
- postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart) // -
|
|
|
|
- postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown) // -
|
|
|
|
- postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
|
|
|
|
- postRestMux.HandleFunc("/rest/system/pause", s.makeDevicePauseHandler(true)) // [device]
|
|
|
|
- postRestMux.HandleFunc("/rest/system/resume", s.makeDevicePauseHandler(false)) // [device]
|
|
|
|
- postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
|
|
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/db/ignores", s.postDBIgnores) // folder
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/db/override", s.postDBOverride) // folder
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/db/revert", s.postDBRevert) // folder
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/folder/versions", s.postFolderVersionsRestore) // folder <body>
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/system/error", s.postSystemError) // <body>
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/system/error/clear", s.postSystemErrorClear) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/system/ping", s.restPing) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/system/reset", s.postSystemReset) // [folder]
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/system/restart", s.postSystemRestart) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/system/shutdown", s.postSystemShutdown) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/system/upgrade", s.postSystemUpgrade) // -
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/system/pause", s.makeDevicePauseHandler(true)) // [device]
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/system/resume", s.makeDevicePauseHandler(false)) // [device]
|
|
|
|
+ restMux.HandlerFunc(http.MethodPost, "/rest/system/debug", s.postSystemDebug) // [enable] [disable]
|
|
|
|
+
|
|
|
|
+ // Config endpoints
|
|
|
|
+
|
|
|
|
+ configBuilder := &configMuxBuilder{
|
|
|
|
+ Router: restMux,
|
|
|
|
+ id: s.id,
|
|
|
|
+ cfg: s.cfg,
|
|
|
|
+ mut: sync.NewMutex(),
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ configBuilder.registerConfig("/rest/config/")
|
|
|
|
+ configBuilder.registerConfigInsync("/rest/config/insync")
|
|
|
|
+ configBuilder.registerFolders("/rest/config/folders")
|
|
|
|
+ configBuilder.registerDevices("/rest/config/devices")
|
|
|
|
+ configBuilder.registerFolder("/rest/config/folders/:id")
|
|
|
|
+ configBuilder.registerDevice("/rest/config/devices/:id")
|
|
|
|
+ configBuilder.registerOptions("/rest/config/options")
|
|
|
|
+ configBuilder.registerLDAP("/rest/config/ldap")
|
|
|
|
+ configBuilder.registerGUI("/rest/config/gui")
|
|
|
|
+
|
|
|
|
+ // Deprecated config endpoints
|
|
|
|
+ configBuilder.registerConfigDeprecated("/rest/system/config") // POST instead of PUT
|
|
|
|
+ configBuilder.registerConfigInsync("/rest/system/config/insync")
|
|
|
|
|
|
// Debug endpoints, not for general use
|
|
// Debug endpoints, not for general use
|
|
debugMux := http.NewServeMux()
|
|
debugMux := http.NewServeMux()
|
|
@@ -305,15 +323,14 @@ func (s *service) serve(ctx context.Context) {
|
|
debugMux.HandleFunc("/rest/debug/cpuprof", s.getCPUProf) // duration
|
|
debugMux.HandleFunc("/rest/debug/cpuprof", s.getCPUProf) // duration
|
|
debugMux.HandleFunc("/rest/debug/heapprof", s.getHeapProf)
|
|
debugMux.HandleFunc("/rest/debug/heapprof", s.getHeapProf)
|
|
debugMux.HandleFunc("/rest/debug/support", s.getSupportBundle)
|
|
debugMux.HandleFunc("/rest/debug/support", s.getSupportBundle)
|
|
- getRestMux.Handle("/rest/debug/", s.whenDebugging(debugMux))
|
|
|
|
|
|
+ restMux.Handler(http.MethodGet, "/rest/debug/", s.whenDebugging(debugMux))
|
|
|
|
|
|
- // A handler that splits requests between the two above and disables
|
|
|
|
- // caching
|
|
|
|
- restMux := noCacheMiddleware(metricsMiddleware(getPostHandler(getRestMux, postRestMux)))
|
|
|
|
|
|
+ // A handler that disables caching
|
|
|
|
+ noCacheRestMux := noCacheMiddleware(metricsMiddleware(restMux))
|
|
|
|
|
|
// The main routing handler
|
|
// The main routing handler
|
|
mux := http.NewServeMux()
|
|
mux := http.NewServeMux()
|
|
- mux.Handle("/rest/", restMux)
|
|
|
|
|
|
+ mux.Handle("/rest/", noCacheRestMux)
|
|
mux.HandleFunc("/qr/", s.getQR)
|
|
mux.HandleFunc("/qr/", s.getQR)
|
|
|
|
|
|
// Serve compiled in assets unless an asset directory was set (for development)
|
|
// Serve compiled in assets unless an asset directory was set (for development)
|
|
@@ -446,19 +463,6 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool {
|
|
return true
|
|
return true
|
|
}
|
|
}
|
|
|
|
|
|
-func getPostHandler(get, post http.Handler) http.Handler {
|
|
|
|
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
- switch r.Method {
|
|
|
|
- case "GET":
|
|
|
|
- get.ServeHTTP(w, r)
|
|
|
|
- case "POST":
|
|
|
|
- post.ServeHTTP(w, r)
|
|
|
|
- default:
|
|
|
|
- http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
func debugMiddleware(h http.Handler) http.Handler {
|
|
func debugMiddleware(h http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
t0 := time.Now()
|
|
t0 := time.Now()
|
|
@@ -837,57 +841,6 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
-func (s *service) getSystemConfig(w http.ResponseWriter, r *http.Request) {
|
|
|
|
- sendJSON(w, s.cfg.RawCopy())
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func (s *service) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
|
|
|
- s.systemConfigMut.Lock()
|
|
|
|
- defer s.systemConfigMut.Unlock()
|
|
|
|
-
|
|
|
|
- to, err := config.ReadJSON(r.Body, s.id)
|
|
|
|
- r.Body.Close()
|
|
|
|
- if err != nil {
|
|
|
|
- l.Warnln("Decoding posted config:", err)
|
|
|
|
- http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if to.GUI.Password != s.cfg.GUI().Password {
|
|
|
|
- if to.GUI.Password != "" && !bcryptExpr.MatchString(to.GUI.Password) {
|
|
|
|
- hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0)
|
|
|
|
- if err != nil {
|
|
|
|
- l.Warnln("bcrypting password:", err)
|
|
|
|
- http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- to.GUI.Password = string(hash)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Activate and save. Wait for the configuration to become active before
|
|
|
|
- // completing the request.
|
|
|
|
-
|
|
|
|
- if wg, err := s.cfg.Replace(to); err != nil {
|
|
|
|
- l.Warnln("Replacing config:", err)
|
|
|
|
- http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
- return
|
|
|
|
- } else {
|
|
|
|
- wg.Wait()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if err := s.cfg.Save(); err != nil {
|
|
|
|
- l.Warnln("Saving config:", err)
|
|
|
|
- http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func (s *service) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
|
|
|
|
- sendJSON(w, map[string]bool{"configInSync": !s.cfg.RequiresRestart()})
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
func (s *service) postSystemRestart(w http.ResponseWriter, r *http.Request) {
|
|
func (s *service) postSystemRestart(w http.ResponseWriter, r *http.Request) {
|
|
s.flushResponse(`{"ok": "restarting"}`, w)
|
|
s.flushResponse(`{"ok": "restarting"}`, w)
|
|
go s.contr.Restart()
|
|
go s.contr.Restart()
|