package v1 import ( "bytes" "encoding/binary" "hash/crc32" "image" "image/color" "image/jpeg" "testing" "github.com/disintegration/imaging" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestShouldStripExif(t *testing.T) { t.Parallel() tests := []struct { name string mimeType string expected bool }{ { name: "JPEG should strip EXIF", mimeType: "image/jpeg", expected: true, }, { name: "JPG should strip EXIF", mimeType: "image/jpg", expected: true, }, { name: "TIFF should strip EXIF", mimeType: "image/tiff", expected: true, }, { name: "WebP should strip EXIF", mimeType: "image/webp", expected: true, }, { name: "HEIC should strip EXIF", mimeType: "image/heic", expected: true, }, { name: "HEIF should strip EXIF", mimeType: "image/heif", expected: true, }, { name: "PNG should not strip EXIF", mimeType: "image/png", expected: false, }, { name: "GIF should not strip EXIF", mimeType: "image/gif", expected: false, }, { name: "text file should not strip EXIF", mimeType: "text/plain", expected: false, }, { name: "PDF should not strip EXIF", mimeType: "application/pdf", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() result := shouldStripExif(tt.mimeType) assert.Equal(t, tt.expected, result) }) } } func TestStripImageExif(t *testing.T) { t.Parallel() // Create a simple test image img := image.NewRGBA(image.Rect(0, 0, 100, 100)) // Fill with red color for y := 0; y < 100; y++ { for x := 0; x < 100; x++ { img.Set(x, y, color.RGBA{R: 255, G: 0, B: 0, A: 255}) } } // Encode as JPEG var buf bytes.Buffer err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 90}) require.NoError(t, err) originalData := buf.Bytes() t.Run("strip JPEG metadata", func(t *testing.T) { t.Parallel() strippedData, err := stripImageExif(originalData, "image/jpeg") require.NoError(t, err) assert.NotEmpty(t, strippedData) // Verify it's still a valid image decodedImg, err := imaging.Decode(bytes.NewReader(strippedData)) require.NoError(t, err) assert.Equal(t, 100, decodedImg.Bounds().Dx()) assert.Equal(t, 100, decodedImg.Bounds().Dy()) }) t.Run("strip JPG metadata (alternate extension)", func(t *testing.T) { t.Parallel() strippedData, err := stripImageExif(originalData, "image/jpg") require.NoError(t, err) assert.NotEmpty(t, strippedData) // Verify it's still a valid image decodedImg, err := imaging.Decode(bytes.NewReader(strippedData)) require.NoError(t, err) assert.NotNil(t, decodedImg) }) t.Run("strip PNG metadata", func(t *testing.T) { t.Parallel() // Encode as PNG first var pngBuf bytes.Buffer err := imaging.Encode(&pngBuf, img, imaging.PNG) require.NoError(t, err) strippedData, err := stripImageExif(pngBuf.Bytes(), "image/png") require.NoError(t, err) assert.NotEmpty(t, strippedData) // Verify it's still a valid image decodedImg, err := imaging.Decode(bytes.NewReader(strippedData)) require.NoError(t, err) assert.Equal(t, 100, decodedImg.Bounds().Dx()) assert.Equal(t, 100, decodedImg.Bounds().Dy()) }) t.Run("handle WebP format by converting to JPEG", func(t *testing.T) { t.Parallel() // WebP format will be converted to JPEG strippedData, err := stripImageExif(originalData, "image/webp") require.NoError(t, err) assert.NotEmpty(t, strippedData) // Verify it's a valid image decodedImg, err := imaging.Decode(bytes.NewReader(strippedData)) require.NoError(t, err) assert.NotNil(t, decodedImg) }) t.Run("handle HEIC format by converting to JPEG", func(t *testing.T) { t.Parallel() strippedData, err := stripImageExif(originalData, "image/heic") require.NoError(t, err) assert.NotEmpty(t, strippedData) // Verify it's a valid image decodedImg, err := imaging.Decode(bytes.NewReader(strippedData)) require.NoError(t, err) assert.NotNil(t, decodedImg) }) t.Run("return error for invalid image data", func(t *testing.T) { t.Parallel() invalidData := []byte("not an image") _, err := stripImageExif(invalidData, "image/jpeg") assert.Error(t, err) assert.Contains(t, err.Error(), "failed to decode image") }) t.Run("return error for empty image data", func(t *testing.T) { t.Parallel() emptyData := []byte{} _, err := stripImageExif(emptyData, "image/jpeg") assert.Error(t, err) }) } func TestValidateImagePixelCountRejectsOversizedDimensions(t *testing.T) { t.Parallel() err := validateImagePixelCount(testPNGHeaderWithDimensions(100_000, 100_000)) require.Error(t, err) require.Contains(t, err.Error(), "image dimensions exceed maximum") } func TestStripImageExifRejectsOversizedDimensionsBeforeDecode(t *testing.T) { t.Parallel() _, err := stripImageExif(testPNGHeaderWithDimensions(100_000, 100_000), "image/png") require.Error(t, err) require.Contains(t, err.Error(), "image dimensions exceed maximum") } func testPNGHeaderWithDimensions(width, height uint32) []byte { var buf bytes.Buffer buf.Write([]byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'}) ihdr := make([]byte, 13) binary.BigEndian.PutUint32(ihdr[0:4], width) binary.BigEndian.PutUint32(ihdr[4:8], height) ihdr[8] = 8 ihdr[9] = 2 writePNGChunk(&buf, "IHDR", ihdr) writePNGChunk(&buf, "IEND", nil) return buf.Bytes() } func writePNGChunk(buf *bytes.Buffer, chunkType string, data []byte) { _ = binary.Write(buf, binary.BigEndian, uint32(len(data))) buf.WriteString(chunkType) buf.Write(data) crc := crc32.ChecksumIEEE(append([]byte(chunkType), data...)) _ = binary.Write(buf, binary.BigEndian, crc) }