mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 22:37:28 +00:00
fix(gopher): serve public files from SFTP storage (#115)
* test(gopher): cover SFTP public file paths * fix(gopher): serve public files from SFTP storage
This commit is contained in:
parent
f83edd4351
commit
be75744248
2 changed files with 35 additions and 2 deletions
|
|
@ -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) {
|
func TestPathTraversalRefused(t *testing.T) {
|
||||||
srv, _ := newTestServer(t)
|
srv, _ := newTestServer(t)
|
||||||
for _, sel := range []string{
|
for _, sel := range []string{
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,7 @@ func (s *Server) newsArticles(group string) Response {
|
||||||
}
|
}
|
||||||
|
|
||||||
// filesRoot lists every non-banned member, each linking to their public files
|
// 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 {
|
func (s *Server) filesRoot() Response {
|
||||||
users, err := s.c.ListUsers(1000)
|
users, err := s.c.ListUsers(1000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -287,7 +287,8 @@ func (s *Server) filesRoot() Response {
|
||||||
// userTree serves a member's per-user area (public_html homepage, or the public
|
// 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 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
|
// "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
|
// 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
|
// path (so any ".." that would escape is neutralised) and the resolved target is
|
||||||
|
|
@ -301,6 +302,9 @@ func (s *Server) userTree(prefix, sel, area string) Response {
|
||||||
return errResp("bad member name")
|
return errResp("bad member name")
|
||||||
}
|
}
|
||||||
base := filepath.Join(s.dataDir, "users", name, area)
|
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 ".."
|
// Confine sub to base. Cleaning as an absolute path drops any leading ".."
|
||||||
// components; the HasPrefix re-check is belt-and-suspenders against edge
|
// components; the HasPrefix re-check is belt-and-suspenders against edge
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue