diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index b6a19d1b..5bf7f73a 100644 --- a/cmd/syncthing/gui.go +++ b/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) { diff --git a/cmd/syncthing/gui_test.go b/cmd/syncthing/gui_test.go index 1ab8176e..b3adfd5a 100644 --- a/cmd/syncthing/gui_test.go +++ b/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 +}