Просмотр исходного кода

cmd/syncthing: Fix /rest/system/browse for folder path completion (fixes #4590)

Two issues since the filesystem migration;

- filepath.Base() doesn't do what the code expected when the path ends
  in a slash, and we make sure the path ends in a slash.

- the return should be fully qualified, not just the last component.

With this change it works as before, and passes the new test for it.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4591
LGTM: imsodin
Jakob Borg 8 лет назад
Родитель
Сommit
a4147d9019
2 измененных файлов с 77 добавлено и 8 удалено
  1. 16 8
      cmd/syncthing/gui.go
  2. 61 0
      cmd/syncthing/gui_test.go

+ 16 - 8
cmd/syncthing/gui.go

@@ -1280,18 +1280,21 @@ func (s *apiService) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
 func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
 	qs := r.URL.Query()
 	current := qs.Get("current")
+
 	// Default value or in case of error unmarshalling ends up being basic fs.
 	var fsType fs.FilesystemType
 	fsType.UnmarshalText([]byte(qs.Get("filesystem")))
 
+	sendJSON(w, browseFiles(current, fsType))
+}
+
+func browseFiles(current string, fsType fs.FilesystemType) []string {
 	if current == "" {
 		filesystem := fs.NewFilesystem(fsType, "")
 		if roots, err := filesystem.Roots(); err == nil {
-			sendJSON(w, roots)
-		} else {
-			http.Error(w, err.Error(), 500)
+			return roots
 		}
-		return
+		return nil
 	}
 	search, _ := fs.ExpandTilde(current)
 	pathSeparator := string(fs.PathSeparator)
@@ -1300,7 +1303,13 @@ func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
 		search = search + pathSeparator
 	}
 	searchDir := filepath.Dir(search)
-	searchFile := filepath.Base(search)
+
+	// The searchFile should be the last component of search, or empty if it
+	// ends with a path separator
+	var searchFile string
+	if !strings.HasSuffix(search, pathSeparator) {
+		searchFile = filepath.Base(search)
+	}
 
 	fs := fs.NewFilesystem(fsType, searchDir)
 
@@ -1310,11 +1319,10 @@ func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
 	for _, subdirectory := range subdirectories {
 		info, err := fs.Stat(subdirectory)
 		if err == nil && info.IsDir() {
-			ret = append(ret, subdirectory+pathSeparator)
+			ret = append(ret, filepath.Join(searchDir, subdirectory)+pathSeparator)
 		}
 	}
-
-	sendJSON(w, ret)
+	return ret
 }
 
 func (s *apiService) getCPUProf(w http.ResponseWriter, r *http.Request) {

+ 61 - 0
cmd/syncthing/gui_test.go

@@ -16,6 +16,8 @@ import (
 	"net"
 	"net/http"
 	"net/http/httptest"
+	"os"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"testing"
@@ -24,6 +26,7 @@ import (
 	"github.com/d4l3k/messagediff"
 	"github.com/syncthing/syncthing/lib/config"
 	"github.com/syncthing/syncthing/lib/events"
+	"github.com/syncthing/syncthing/lib/fs"
 	"github.com/syncthing/syncthing/lib/protocol"
 	"github.com/syncthing/syncthing/lib/sync"
 	"github.com/thejerf/suture"
@@ -957,3 +960,61 @@ func TestEventMasks(t *testing.T) {
 		t.Errorf("should have returned a valid, non-default event sub")
 	}
 }
+
+func TestBrowse(t *testing.T) {
+	pathSep := string(os.PathSeparator)
+
+	tmpDir, err := ioutil.TempDir("", "syncthing")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpDir)
+
+	if err := os.Mkdir(filepath.Join(tmpDir, "dir"), 0755); err != nil {
+		t.Fatal(err)
+	}
+	if err := ioutil.WriteFile(filepath.Join(tmpDir, "file"), []byte("hello"), 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	// We expect completion to return the full path to the completed
+	// directory, with an ending slash.
+	dirPath := filepath.Join(tmpDir, "dir") + pathSep
+
+	cases := []struct {
+		current string
+		returns []string
+	}{
+		// The direcotory without slash is completed to one with slash.
+		{tmpDir, []string{tmpDir + pathSep}},
+		// With slash it's completed to its contents.
+		// Dirs are given pathSeps.
+		// Files are not returned.
+		{tmpDir + pathSep, []string{dirPath}},
+		// Globbing is automatic based on prefix.
+		{tmpDir + pathSep + "d", []string{dirPath}},
+		{tmpDir + pathSep + "di", []string{dirPath}},
+		{tmpDir + pathSep + "dir", []string{dirPath}},
+		{tmpDir + pathSep + "f", nil},
+		{tmpDir + pathSep + "q", nil},
+	}
+
+	for _, tc := range cases {
+		ret := browseFiles(tc.current, fs.FilesystemTypeBasic)
+		if !equalStrings(ret, tc.returns) {
+			t.Errorf("browseFiles(%q) => %q, expected %q", tc.current, ret, tc.returns)
+		}
+	}
+}
+
+func equalStrings(a, b []string) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i := range a {
+		if a[i] != b[i] {
+			return false
+		}
+	}
+	return true
+}