Files
seaweedfs/weed/s3api/s3api_object_handlers_dir_test.go
T
Chris LuandGitHub d0e47cf4da s3: answer directory-path probes like AWS so Flink savepoints restore (#10225)
* s3: answer application/x-directory for a directory without a stored mime

A real directory reached via a trailing-slash GET/HEAD answered the
octet-stream default when it had no stored mime, so Hadoop-style S3
filesystems (flink-s3-fs-presto and friends) classified the path as a
0-byte file instead of a directory and then failed reading it as one.
Answer application/x-directory, the marker type those clients probe
for. Stored mimes still echo verbatim, and a file promoted to a
directory keeps octet-stream for its data.

* s3: 404 GET and HEAD on a bare directory path consistently

A directory with no object data of its own answered differently per
path: plain GET gave an empty 200, ranged GET and non-versioned HEAD
gave 404, and on versioned buckets the null-version fallback adopted
the filer directory as a 0-byte object and answered 200. Clients that
probe HEAD-then-GET took the 200s at face value, treated the path as
an empty file, and never fell back to LIST-based directory discovery.

Answer 404 for a bare directory path everywhere, which is what AWS
returns for a prefix. A file promoted to a directory keeps its data
and stays retrievable.
2026-07-04 00:41:57 -07:00

50 lines
1.4 KiB
Go

package s3api
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
)
func TestServeDirectoryContentContentType(t *testing.T) {
s3a := &S3ApiServer{}
tests := []struct {
name string
entry *filer_pb.Entry
wantType string
}{
{
name: "bare directory answers the directory marker type",
entry: &filer_pb.Entry{Name: "savepoint", IsDirectory: true, Attributes: &filer_pb.FuseAttributes{}},
wantType: "application/x-directory",
},
{
name: "promoted file with data keeps octet-stream",
entry: &filer_pb.Entry{Name: "promoted", IsDirectory: true, Content: []byte("data"), Attributes: &filer_pb.FuseAttributes{FileSize: 4}},
wantType: "application/octet-stream",
},
{
name: "stored mime is echoed verbatim",
entry: &filer_pb.Entry{Name: "marker", IsDirectory: true, Attributes: &filer_pb.FuseAttributes{Mime: "httpd/unix-directory"}},
wantType: "httpd/unix-directory",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodHead, "/bucket/dir/", nil)
rec := httptest.NewRecorder()
s3a.serveDirectoryContent(rec, req, tt.entry)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200", rec.Code)
}
if got := rec.Header().Get("Content-Type"); got != tt.wantType {
t.Fatalf("Content-Type = %q, want %q", got, tt.wantType)
}
})
}
}