Skip to content
Draft
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
5 changes: 4 additions & 1 deletion pkg/httpserver/httpserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ func New(options *Options) (*HTTPServer, error) {

var httpHandler http.Handler
if options.Python {
httpHandler = PythonStyle(dir.(http.Dir))
// PythonStyle now accepts http.FileSystem so SandboxFileSystem is
// honoured when -sandbox is also set (previously a type assertion to
// http.Dir would panic at startup when Sandbox=true).
httpHandler = PythonStyle(dir)
} else {
httpHandler = http.FileServer(dir)
}
Expand Down
48 changes: 48 additions & 0 deletions pkg/httpserver/httpserver_security_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package httpserver

import (
"net/http"
"testing"
)

// TestPythonStyleAcceptFileSystem verifies that PythonStyle accepts an
// http.FileSystem interface (not a concrete http.Dir). Before the fix,
// httpserver.New() panicked with
//
// interface conversion: http.FileSystem is httpserver.SandboxFileSystem, not http.Dir
//
// when both -sandbox and -python flags were set, because PythonStyle only
// accepted http.Dir via a type assertion.
func TestPythonStyleAcceptFileSystem(t *testing.T) {
// SandboxFileSystem implements http.FileSystem but is NOT http.Dir.
// Passing it to PythonStyle must not panic.
sbfs := SandboxFileSystem{
fs: http.Dir(t.TempDir()),
RootFolder: t.TempDir(),
}
// This call would panic on the old code because of dir.(http.Dir).
handler := PythonStyle(sbfs)
if handler == nil {
t.Fatal("PythonStyle returned nil handler")
}
}

// TestNewServerSandboxPythonNoPanic mirrors the exact code path in New()
// that used to panic: Sandbox=true + Python=true.
func TestNewServerSandboxPythonNoPanic(t *testing.T) {
dir := t.TempDir()
opts := &Options{
Folder: dir,
ListenAddress: "127.0.0.1:0",
Sandbox: true,
Python: true,
}
// Before the fix this returned a panic, not an error.
srv, err := New(opts)
if err != nil {
t.Fatalf("New() returned unexpected error: %v", err)
}
if srv == nil {
t.Fatal("New() returned nil server")
}
}
37 changes: 23 additions & 14 deletions pkg/httpserver/pythonliststyle.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
)

const (
Expand All @@ -30,7 +28,7 @@ const (

type pythonStyleHandler struct {
origWriter http.ResponseWriter
root http.Dir
root http.FileSystem
}

func (h *pythonStyleHandler) Header() http.Header {
Expand Down Expand Up @@ -75,22 +73,33 @@ func (h *pythonStyleHandler) WriteHeader(statusCode int) {
}

func (h *pythonStyleHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
target := filepath.Join(string(h.root), filepath.Clean(request.URL.Path))
file, err := os.Stat(target)
// Use the FileSystem's Open to respect sandbox containment checks.
// This avoids building raw OS paths and correctly delegates to SandboxFileSystem
// or any other http.FileSystem implementation.
f, err := h.root.Open(request.URL.Path)
if err != nil {
http.NotFound(writer, request)
return
}
defer f.Close()

if err != nil || !file.IsDir() {
http.ServeFile(writer, request, target)
fi, err := f.Stat()
if err != nil || !fi.IsDir() {
// Not a directory: serve through the FileSystem handler
http.FileServer(h.root).ServeHTTP(writer, request)
return
} else {
_, _ = fmt.Fprintf(writer, htmlHeader, request.URL.Path)
_, _ = fmt.Fprintf(writer, "<h1>Directory listing for %s</h1>\n<hr>\n", request.URL.Path)
h.origWriter = writer
http.ServeFile(h, request, target)
_, _ = fmt.Fprint(writer, htmlFooter)
}
_, _ = fmt.Fprintf(writer, htmlHeader, request.URL.Path)
_, _ = fmt.Fprintf(writer, "<h1>Directory listing for %s</h1>\n<hr>\n", request.URL.Path)
h.origWriter = writer
http.FileServer(h.root).ServeHTTP(h, request)
_, _ = fmt.Fprint(writer, htmlFooter)
}

func PythonStyle(root http.Dir) http.Handler {
// PythonStyle returns an HTTP handler that renders directory listings in Python's
// SimpleHTTPServer style. It accepts any http.FileSystem so that sandbox
// containment (SandboxFileSystem) is honoured even in this mode.
func PythonStyle(root http.FileSystem) http.Handler {
return &pythonStyleHandler{
root: root,
}
Expand Down
Loading