fix(datasets): accept BigTIFF headers and sanitise object keys
F1: ValidateFileContent rejected the pipeline's own output. GDAL writes a
BigTIFF header ("II+\0" / "MM\0+") whenever BIGTIFF=IF_SAFER kicks in for a
large raster, so accept both alongside the classic TIFF magic and reword the
error to "file is not a valid TIFF/BigTIFF GeoTIFF". GodalIO.Create now
delegates to an unexported createGTiff so a test can force BIGTIFF=YES and
assert the produced header passes the check.
F3: a user-supplied filename went straight into the S3 object key for dataset
uploads and for both dataset and dataset-group SLD uploads, so "../../x.sld"
escaped the dataset's own prefix. Reduce every such name to its base with a
shared safeObjectName helper, rejecting names with no usable base.
F9: rename the shadowing inner "raw" in the PATCH dg_id case to "s".
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1926,3 +1926,86 @@ func TestDatasetService_CreateRasterFromFile_MissingFile(t *testing.T) {
|
||||
t.Fatal("want an error for a missing file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeObjectName(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"plain name", "style.sld", "style.sld", false},
|
||||
{"trims space", " style.sld ", "style.sld", false},
|
||||
{"nested path reduced to base", "a/b/style.sld", "style.sld", false},
|
||||
{"traversal reduced to base", "../../x.sld", "x.sld", false},
|
||||
{"absolute path reduced to base", "/etc/passwd", "passwd", false},
|
||||
{"dotdot alone", "..", "", true},
|
||||
{"dot alone", ".", "", true},
|
||||
{"root alone", "/", "", true},
|
||||
{"empty", "", "", true},
|
||||
{"blank", " ", "", true},
|
||||
{"trailing slash", "a/b/", "b", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got, err := safeObjectName(c.in)
|
||||
if c.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("want error, got %q", got)
|
||||
}
|
||||
if !errors.Is(err, domain.ErrValidation) {
|
||||
t.Fatalf("want ErrValidation, got %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != c.want {
|
||||
t.Fatalf("safeObjectName(%q) = %q, want %q", c.in, got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatasetService_UploadSLD_RejectsPathTraversal(t *testing.T) {
|
||||
id := uuid.New()
|
||||
repo := newStubDatasetRepo()
|
||||
repo.store[id] = domain.Dataset{ID: id, FileType: domain.FileTypeRaster, StorageKey: "uid/dem.tif"}
|
||||
store := &stubStore{}
|
||||
svc := newDatasetService(repo, store, true)
|
||||
|
||||
in := validSLDUpload()
|
||||
in.Filename = "../../other/sld/evil.sld"
|
||||
got, err := svc.UploadSLD(context.Background(), id, in)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got.SldStorageKey == nil || *got.SldStorageKey != "uid/sld/evil.sld" {
|
||||
t.Fatalf("sld key escaped the dataset prefix: %v", got.SldStorageKey)
|
||||
}
|
||||
if len(store.put) != 1 || !strings.HasPrefix(store.put[0], "uid/sld/") || !strings.HasSuffix(store.put[0], "evil.sld") {
|
||||
t.Fatalf("object stored outside the dataset sld prefix: %v", store.put)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatasetService_Upload_SanitisesTraversalFilename(t *testing.T) {
|
||||
repo := newStubDatasetRepo()
|
||||
store := &stubStore{}
|
||||
svc := newDatasetService(repo, store, true)
|
||||
|
||||
in := validUpload()
|
||||
in.Filename = "../../evil.geojson"
|
||||
got, err := svc.Upload(context.Background(), in)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// The key is "<uuid>/<basename>": exactly two segments, no traversal.
|
||||
parts := strings.Split(got.StorageKey, "/")
|
||||
if len(parts) != 2 || parts[1] != "evil.geojson" {
|
||||
t.Fatalf("storage key escaped its uuid prefix: %q", got.StorageKey)
|
||||
}
|
||||
if len(store.put) != 1 || store.put[0] != got.StorageKey {
|
||||
t.Fatalf("object stored under an unexpected key: %v", store.put)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user