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:
@@ -66,10 +66,17 @@ func (r *godalReader) Close() error { return r.ds.Close() }
|
||||
type godalWriter struct{ ds *godal.Dataset }
|
||||
|
||||
func (g *GodalIO) Create(_ context.Context, path string, grid Grid, o WriterOptions) (Writer, error) {
|
||||
return createGTiff(path, grid, o, "IF_SAFER")
|
||||
}
|
||||
|
||||
// createGTiff writes a tiled Int16 GeoTIFF. bigTIFF is the value of the
|
||||
// BIGTIFF creation option ("IF_SAFER" in production); it is a parameter so
|
||||
// tests can force a BigTIFF header.
|
||||
func createGTiff(path string, grid Grid, o WriterOptions, bigTIFF string) (Writer, error) {
|
||||
tile := fmt.Sprint(o.TileSize)
|
||||
ds, err := godal.Create(godal.GTiff, path, 1, godal.Int16, grid.Width, grid.Height,
|
||||
godal.CreationOption("TILED=YES", "BLOCKXSIZE="+tile, "BLOCKYSIZE="+tile,
|
||||
"COMPRESS="+o.Compression, "PREDICTOR=2", "BIGTIFF=IF_SAFER"))
|
||||
"COMPRESS="+o.Compression, "PREDICTOR=2", "BIGTIFF="+bigTIFF))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create raster %s: %w", path, err)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package raster
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gis/internal/domain"
|
||||
)
|
||||
|
||||
func TestGodalIO_RoundTrip(t *testing.T) {
|
||||
@@ -60,3 +65,39 @@ func TestGodalIO_RoundTrip(t *testing.T) {
|
||||
t.Fatal("SameGrid should be false for shifted origin")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGodalIO_BigTIFFPassesContentValidation guards the upload magic-byte
|
||||
// check against the pipeline's own output: GDAL writes a BigTIFF header
|
||||
// (version 43) whenever BIGTIFF is YES or IF_SAFER kicks in for a large
|
||||
// raster, and domain.ValidateFileContent must accept it.
|
||||
func TestGodalIO_BigTIFFPassesContentValidation(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "big.tif")
|
||||
grid := Grid{Width: 8, Height: 8, GeoTransform: [6]float64{100, 30, 0, 200, 0, -30}, ProjectionWKT: "EPSG:32642"}
|
||||
|
||||
w, err := createGTiff(path, grid, DefaultWriterOptions(), "YES")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.WriteInt16(Window{0, 0, 8, 8}, make([]int16, 8*8)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
head := make([]byte, 4)
|
||||
if _, err := io.ReadFull(f, head); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.HasPrefix(head, []byte("II+\x00")) && !bytes.HasPrefix(head, []byte("MM\x00+")) {
|
||||
t.Fatalf("BIGTIFF=YES did not produce a BigTIFF header: % x", head)
|
||||
}
|
||||
if err := domain.ValidateFileContent(".tif", head); err != nil {
|
||||
t.Fatalf("ValidateFileContent rejected our own BigTIFF output: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user