Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions internal/gopher/gopher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,35 @@ func TestHomepageFileAndDir(t *testing.T) {
}
}

func TestPublicFilesUseSFTPStorageRoot(t *testing.T) {
srv, dataDir := newTestServer(t)

publicRoot := filepath.Join(dataDir, "files", "public", "alice")
mustMkdir(t, publicRoot)
mustWrite(t, filepath.Join(publicRoot, "shared.txt"), "uploaded through sftp")

legacyRoot := filepath.Join(dataDir, "users", "alice", "public")
mustMkdir(t, legacyRoot)
mustWrite(t, filepath.Join(legacyRoot, "legacy.txt"), "stale location")

dir := srv.Resolve("/files/~alice", false, "")
if dir.Kind != KindMenu {
t.Fatalf("public files root should be a dir menu, got %v", dir.Kind)
}
wire := string(dir.Wire())
if !strings.Contains(wire, "/files/~alice/shared.txt") {
t.Errorf("public files should list SFTP uploads:\n%s", wire)
}
if strings.Contains(wire, "legacy.txt") {
t.Errorf("public files must not read the legacy user directory:\n%s", wire)
}

file := srv.Resolve("/files/~alice/shared.txt", false, "")
if file.Kind != KindText || file.Text != "uploaded through sftp" {
t.Errorf("SFTP public file should be served as text: %+v", file)
}
}

func TestPathTraversalRefused(t *testing.T) {
srv, _ := newTestServer(t)
for _, sel := range []string{
Expand Down
8 changes: 6 additions & 2 deletions internal/gopher/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func (s *Server) newsArticles(group string) Response {
}

// filesRoot lists every non-banned member, each linking to their public files
// area (<data>/users/<name>/public), mirroring the anonymous web files surface.
// area (<data>/files/public/<name>), mirroring the anonymous web files surface.
func (s *Server) filesRoot() Response {
users, err := s.c.ListUsers(1000)
if err != nil {
Expand All @@ -287,7 +287,8 @@ func (s *Server) filesRoot() Response {
// userTree serves a member's per-user area (public_html homepage, or the public
// files area) as gopher content. prefix is the selector root ("" for homepages,
// "files" for the files area); sel is the remainder after the prefix, of the form
// "~name[/subpath]". area is the on-disk subdirectory under <data>/users/<name>.
// "~name[/subpath]". Homepages live under <data>/users/<name>/<area>; public
// files share the SFTP service's <data>/files/public/<name> storage root.
//
// The subpath is confined to the member's area: it is cleaned as an absolute
// path (so any ".." that would escape is neutralised) and the resolved target is
Expand All @@ -301,6 +302,9 @@ func (s *Server) userTree(prefix, sel, area string) Response {
return errResp("bad member name")
}
base := filepath.Join(s.dataDir, "users", name, area)
if prefix == "files" {
base = filepath.Join(s.dataDir, "files", "public", name)
}

// Confine sub to base. Cleaning as an absolute path drops any leading ".."
// components; the HasPrefix re-check is belt-and-suspenders against edge
Expand Down
Loading