├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── exif-remove-tool ├── README.md ├── img │ ├── jpg │ │ ├── 11-tests.jpg │ │ ├── 22-canon_tags.jpg │ │ ├── 45-gps_ifd.jpg │ │ ├── 67-0_length_string.jpg │ │ ├── BlueSquare.jpg │ │ ├── Canon_40D_photoshop_import.jpg │ │ ├── DSCN0010.jpg │ │ ├── Fujifilm_FinePix6900ZOOM.jpg │ │ ├── Fujifilm_FinePix_E500.jpg │ │ ├── Kodak_CX7530.jpg │ │ ├── Konica_Minolta_DiMAGE_Z3.jpg │ │ ├── Nikon_COOLPIX_P1.jpg │ │ ├── Nikon_D70.jpg │ │ ├── foto_no_exif.jpg │ │ ├── fujifilm-dx10.jpg │ │ ├── fujifilm-mx1700.jpg │ │ ├── gps.jpg │ │ ├── jolla.jpg │ │ ├── kodak-dc210.jpg │ │ ├── kodak-dc240.jpg │ │ ├── landscape_5.jpg │ │ ├── nikon-e950.jpg │ │ └── sony-powershota5.jpg │ └── png │ │ ├── Selection_058.png │ │ ├── exif.png │ │ ├── graf1.png │ │ ├── imageTextN.png │ │ ├── mask.png │ │ ├── ml.png │ │ ├── notes.png │ │ ├── rubberwhale1.png │ │ ├── rubberwhale2.png │ │ └── sudoku.png ├── main.go └── main_test.go ├── exif_remove.go ├── exif_remove_test.go ├── go.mod ├── go.sum ├── png_crc_fix.go └── readme.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | codecov: codecov/codecov@1.0.2 4 | jobs: 5 | build: 6 | docker: 7 | - image: circleci/golang:1.12 8 | 9 | working_directory: /go/src/github.com/scottleedavis/go-exif-remove 10 | steps: 11 | - checkout 12 | 13 | # specify any bash command here prefixed with `run: ` 14 | - run: go get -v -t -d ./... 15 | - run: go test -v -coverprofile cp.out 16 | - codecov/upload: 17 | file: cp.out 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | *.iml 15 | .idea 16 | exif-remove-tool/img_output 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 scott lee davis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-exif-remove 2 | [![Build Status](https://img.shields.io/circleci/project/github/scottleedavis/go-exif-remove/master.svg)](https://circleci.com/gh/scottleedavis/go-exif-remove) [![codecov](https://codecov.io/gh/scottleedavis/go-exif-remove/branch/master/graph/badge.svg)](https://codecov.io/gh/scottleedavis/go-exif-remove) [![GoDoc](https://godoc.org/github.com/scottleedavis/go-exif-remove?status.svg)](https://godoc.org/github.com/scottleedavis/go-exif-remove) 3 | 4 | 5 | Removes EXIF information from JPG and PNG files 6 | 7 | Uses [go-exif](https://github.com/dsoprea/go-exif) to extract EXIF information and overwrites the EXIF region. 8 | 9 | ```go 10 | import "github.com/scottleedavis/go-exif-remove" 11 | 12 | noExifBytes, err := exifremove.Remove(imageBytes) 13 | ``` 14 | 15 | See example usage in [exif-remove-tool](exif-remove-tool) 16 | 17 | -------------------------------------------------------------------------------- /exif-remove-tool/README.md: -------------------------------------------------------------------------------- 1 | ```bash 2 | #run against all in img folder 3 | go run main.go 4 | 5 | #run against single file 6 | go run main.go img/jpg/11-tests.jpg 7 | ``` 8 | -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/11-tests.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/11-tests.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/22-canon_tags.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/22-canon_tags.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/45-gps_ifd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/45-gps_ifd.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/67-0_length_string.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/67-0_length_string.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/BlueSquare.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/BlueSquare.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/Canon_40D_photoshop_import.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/Canon_40D_photoshop_import.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/DSCN0010.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/DSCN0010.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/Fujifilm_FinePix6900ZOOM.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/Fujifilm_FinePix6900ZOOM.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/Fujifilm_FinePix_E500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/Fujifilm_FinePix_E500.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/Kodak_CX7530.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/Kodak_CX7530.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/Konica_Minolta_DiMAGE_Z3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/Konica_Minolta_DiMAGE_Z3.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/Nikon_COOLPIX_P1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/Nikon_COOLPIX_P1.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/Nikon_D70.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/Nikon_D70.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/foto_no_exif.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/foto_no_exif.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/fujifilm-dx10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/fujifilm-dx10.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/fujifilm-mx1700.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/fujifilm-mx1700.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/gps.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/gps.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/jolla.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/jolla.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/kodak-dc210.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/kodak-dc210.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/kodak-dc240.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/kodak-dc240.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/landscape_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/landscape_5.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/nikon-e950.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/nikon-e950.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/jpg/sony-powershota5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/jpg/sony-powershota5.jpg -------------------------------------------------------------------------------- /exif-remove-tool/img/png/Selection_058.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/png/Selection_058.png -------------------------------------------------------------------------------- /exif-remove-tool/img/png/exif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/png/exif.png -------------------------------------------------------------------------------- /exif-remove-tool/img/png/graf1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/png/graf1.png -------------------------------------------------------------------------------- /exif-remove-tool/img/png/imageTextN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/png/imageTextN.png -------------------------------------------------------------------------------- /exif-remove-tool/img/png/mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/png/mask.png -------------------------------------------------------------------------------- /exif-remove-tool/img/png/ml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/png/ml.png -------------------------------------------------------------------------------- /exif-remove-tool/img/png/notes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/png/notes.png -------------------------------------------------------------------------------- /exif-remove-tool/img/png/rubberwhale1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/png/rubberwhale1.png -------------------------------------------------------------------------------- /exif-remove-tool/img/png/rubberwhale2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/png/rubberwhale2.png -------------------------------------------------------------------------------- /exif-remove-tool/img/png/sudoku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scottleedavis/go-exif-remove/7e059d59340538e639ab516ea037dec825d5b662/exif-remove-tool/img/png/sudoku.png -------------------------------------------------------------------------------- /exif-remove-tool/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "image" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/scottleedavis/go-exif-remove" 13 | ) 14 | 15 | func main() { 16 | 17 | if len(os.Args) == 1 { 18 | var files []string 19 | root := "img" 20 | err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 21 | if path != "img" && path != "img/png" && path != "img/jpg" { 22 | files = append(files, path) 23 | } 24 | return nil 25 | }) 26 | if err != nil { 27 | panic(err) 28 | } 29 | pass := 0 30 | fail := 0 31 | for _, file := range files { 32 | fmt.Println(file) 33 | if b, err := handleFile(file); err != nil { 34 | fail += 1 35 | } else { 36 | pass += 1 37 | f := filepath.Base(file) 38 | ioutil.WriteFile("img_output/"+f, b, 0644) 39 | } 40 | fmt.Println() 41 | } 42 | 43 | percentage := 100 * pass / (pass + fail) 44 | fmt.Printf("Results (%v%%): %v pass, %v fail \n", int(percentage), pass, fail) 45 | } else { 46 | path := os.Args[1] 47 | if b, err := handleFile(path); err != nil { 48 | fmt.Printf(err.Error()) 49 | } else { 50 | file := filepath.Base(path) 51 | ioutil.WriteFile("img_output/"+file, b, 0644) 52 | } 53 | } 54 | 55 | } 56 | 57 | func handleFile(filepath string) ([]byte, error) { 58 | if data, err := ioutil.ReadFile(filepath); err != nil { 59 | fmt.Printf(err.Error()) 60 | return nil, err 61 | } else { 62 | if _, _, err := image.Decode(bytes.NewReader(data)); err != nil { 63 | fmt.Printf("ERROR: original image is corrupt" + err.Error() + "\n") 64 | return nil, err 65 | } 66 | if filtered, err := exifremove.Remove(data); err != nil { 67 | fmt.Printf("* " + err.Error() + "\n") 68 | return nil, errors.New(err.Error()) 69 | } else { 70 | if _, _, err = image.Decode(bytes.NewReader(filtered)); err != nil { 71 | fmt.Printf("ERROR: filtered image is corrupt" + err.Error() + "\n") 72 | return nil, err 73 | } 74 | return filtered, nil 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /exif-remove-tool/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "os/exec" 7 | "regexp" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestMain(m *testing.M) { 14 | code := m.Run() 15 | os.Exit(code) 16 | } 17 | 18 | func TestImages(t *testing.T) { 19 | cmd := exec.Command( 20 | "go", "run", "main.go") 21 | 22 | b := new(bytes.Buffer) 23 | cmd.Stdout = b 24 | cmd.Stderr = b 25 | 26 | err := cmd.Run() 27 | actualRaw := b.Bytes() 28 | 29 | assert.Nil(t, err) 30 | 31 | var re = regexp.MustCompile(`0 fail`) 32 | matches := re.FindStringSubmatch(string(actualRaw)) 33 | assert.True(t, len(matches) == 1, "Should parse all files in img folder") 34 | } 35 | -------------------------------------------------------------------------------- /exif_remove.go: -------------------------------------------------------------------------------- 1 | package exifremove 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "image/jpeg" 8 | "image/png" 9 | 10 | "github.com/dsoprea/go-exif" 11 | "github.com/dsoprea/go-jpeg-image-structure" 12 | "github.com/dsoprea/go-png-image-structure" 13 | ) 14 | 15 | func Remove(data []byte) ([]byte, error) { 16 | 17 | const ( 18 | JpegMediaType = "jpeg" 19 | PngMediaType = "png" 20 | OtherMediaType = "other" 21 | StartBytes = 0 22 | EndBytes = 0 23 | ) 24 | 25 | type MediaContext struct { 26 | MediaType string 27 | RootIfd *exif.Ifd 28 | RawExif []byte 29 | Media interface{} 30 | } 31 | 32 | jmp := jpegstructure.NewJpegMediaParser() 33 | pmp := pngstructure.NewPngMediaParser() 34 | filtered := []byte{} 35 | 36 | if jmp.LooksLikeFormat(data) { 37 | 38 | sl, err := jmp.ParseBytes(data) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | _, rawExif, err := sl.Exif() 44 | if err != nil { 45 | return data, nil 46 | } 47 | 48 | startExifBytes := StartBytes 49 | endExifBytes := EndBytes 50 | 51 | if bytes.Contains(data, rawExif) { 52 | for i := 0; i < len(data)-len(rawExif); i++ { 53 | if bytes.Compare(data[i:i+len(rawExif)], rawExif) == 0 { 54 | startExifBytes = i 55 | endExifBytes = i + len(rawExif) 56 | break 57 | } 58 | } 59 | fill := make([]byte, len(data[startExifBytes:endExifBytes])) 60 | copy(data[startExifBytes:endExifBytes], fill) 61 | } 62 | 63 | filtered = data 64 | 65 | _, err = jpeg.Decode(bytes.NewReader(filtered)) 66 | if err != nil { 67 | return nil, errors.New("EXIF removal corrupted " + err.Error()) 68 | } 69 | 70 | } else if pmp.LooksLikeFormat(data) { 71 | 72 | cs, err := pmp.ParseBytes(data) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | _, rawExif, err := cs.Exif() 78 | if err != nil { 79 | return data, nil 80 | } 81 | 82 | startExifBytes := StartBytes 83 | endExifBytes := EndBytes 84 | 85 | if bytes.Contains(data, rawExif) { 86 | for i := 0; i < len(data)-len(rawExif); i++ { 87 | if bytes.Compare(data[i:i+len(rawExif)], rawExif) == 0 { 88 | startExifBytes = i 89 | endExifBytes = i + len(rawExif) 90 | break 91 | } 92 | } 93 | fill := make([]byte, len(data[startExifBytes:endExifBytes])) 94 | copy(data[startExifBytes:endExifBytes], fill) 95 | } 96 | 97 | filtered = data 98 | 99 | chunks := readPNGChunks(bytes.NewReader(filtered)) 100 | 101 | for _, chunk := range chunks { 102 | if !chunk.CRCIsValid() { 103 | offset := int(chunk.Offset) + 8 + int(chunk.Length) 104 | crc := chunk.CalculateCRC() 105 | 106 | buf := new(bytes.Buffer) 107 | binary.Write(buf, binary.BigEndian, crc) 108 | crcBytes := buf.Bytes() 109 | 110 | copy(filtered[offset:], crcBytes) 111 | } 112 | } 113 | 114 | chunks = readPNGChunks(bytes.NewReader(filtered)) 115 | for _, chunk := range chunks { 116 | if !chunk.CRCIsValid() { 117 | return nil, errors.New("EXIF removal failed CRC") 118 | } 119 | } 120 | 121 | _, err = png.Decode(bytes.NewReader(filtered)) 122 | if err != nil { 123 | return nil, errors.New("EXIF removal corrupted " + err.Error()) 124 | } 125 | 126 | } 127 | 128 | return filtered, nil 129 | } 130 | -------------------------------------------------------------------------------- /exif_remove_test.go: -------------------------------------------------------------------------------- 1 | package exifremove 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | jpegstructure "github.com/dsoprea/go-jpeg-image-structure" 8 | pngstructure "github.com/dsoprea/go-png-image-structure" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestRemove(t *testing.T) { 13 | 14 | data, err := ioutil.ReadFile("exif-remove-tool/img/jpg/11-tests.jpg") 15 | assert.Nil(t, err) 16 | filtered, err := Remove(data) 17 | assert.Nil(t, err) 18 | jmp := jpegstructure.NewJpegMediaParser() 19 | sl, err := jmp.ParseBytes(filtered) 20 | _, _, err = sl.Exif() 21 | assert.NotNil(t, err) 22 | 23 | data, err = ioutil.ReadFile("exif-remove-tool/img/png/exif.png") 24 | assert.Nil(t, err) 25 | filtered, err = Remove(data) 26 | assert.Nil(t, err) 27 | pmp := pngstructure.NewPngMediaParser() 28 | cs, err := pmp.ParseBytes(filtered) 29 | _, _, err = cs.Exif() 30 | assert.NotNil(t, err) 31 | 32 | } 33 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/scottleedavis/go-exif-remove 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 // indirect 7 | github.com/dsoprea/go-exif v0.0.0-20190901173045-3ce78807c90f 8 | github.com/dsoprea/go-jpeg-image-structure v0.0.0-20190422055009-d6f9ba25cf48 9 | github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 // indirect 10 | github.com/dsoprea/go-png-image-structure v0.0.0-20190624104353-c9b28dcdc5c8 11 | github.com/go-errors/errors v1.0.1 // indirect 12 | github.com/golang/geo v0.0.0-20190812012225-f41920e961ce // indirect 13 | github.com/kr/pretty v0.1.0 // indirect 14 | github.com/stretchr/testify v1.4.0 15 | golang.org/x/net v0.7.0 // indirect 16 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/dsoprea/go-exif v0.0.0-20190901173045-3ce78807c90f h1:vqfYiZ+xF0xJvl9SZ1kovmMgKjaGZIz/Hn8JDQdyd9A= 5 | github.com/dsoprea/go-exif v0.0.0-20190901173045-3ce78807c90f/go.mod h1:DmMpU91/Ax6BAwoRkjgRCr2rmgEgS4tsmatfV7M+U+c= 6 | github.com/dsoprea/go-jpeg-image-structure v0.0.0-20190422055009-d6f9ba25cf48 h1:9zARagUAxQJjibcDy+0+koUMR6sbX38L49Bk2Vni628= 7 | github.com/dsoprea/go-jpeg-image-structure v0.0.0-20190422055009-d6f9ba25cf48/go.mod h1:H1hAaFyv9cRV1ywoHvaqVoNSThBvWZ0JarRBcV+FSnE= 8 | github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 h1:VGFnZAcLwPpt1sHlAxml+pGLZz9A2s+K/s1YNhPC91Y= 9 | github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= 10 | github.com/dsoprea/go-png-image-structure v0.0.0-20190624104353-c9b28dcdc5c8 h1:SVQfy5rBFZXzvGkU2MZ0RzpS912/1sJrEJ+FMmeaC9U= 11 | github.com/dsoprea/go-png-image-structure v0.0.0-20190624104353-c9b28dcdc5c8/go.mod h1:Bf0nmcDFFRQBjZwr9qY6c0zTxKQa+Q8YWZmlYxXGxY0= 12 | github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= 13 | github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 14 | github.com/golang/geo v0.0.0-20190812012225-f41920e961ce h1:rqIKPpIcEgiNn0KYNFYD34TbMO86l4woyhNzSP+Oegs= 15 | github.com/golang/geo v0.0.0-20190812012225-f41920e961ce/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= 16 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 17 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 18 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 19 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 20 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 21 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 22 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 23 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 24 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 25 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 26 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 27 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 28 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 29 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 30 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 31 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 32 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 33 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 34 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 35 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 36 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 37 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 38 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 44 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 45 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 46 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 47 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 48 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 49 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 50 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 51 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 52 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 53 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 54 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 55 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 56 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 57 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 58 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 59 | -------------------------------------------------------------------------------- /png_crc_fix.go: -------------------------------------------------------------------------------- 1 | package exifremove 2 | 3 | // borrowed heavily from https://github.com/landaire/png-crc-fix/blob/master/main.go 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | "fmt" 9 | "hash/crc32" 10 | "io" 11 | "os" 12 | ) 13 | 14 | const chunkStartOffset = 8 15 | const endChunk = "IEND" 16 | 17 | type pngChunk struct { 18 | Offset int64 19 | Length uint32 20 | Type [4]byte 21 | Data []byte 22 | CRC uint32 23 | } 24 | 25 | func (p pngChunk) String() string { 26 | return fmt.Sprintf("%s@%x - %X - Valid CRC? %v", p.Type, p.Offset, p.CRC, p.CRCIsValid()) 27 | } 28 | 29 | func (p pngChunk) Bytes() []byte { 30 | var buffer bytes.Buffer 31 | 32 | binary.Write(&buffer, binary.BigEndian, p.Type) 33 | buffer.Write(p.Data) 34 | 35 | return buffer.Bytes() 36 | } 37 | 38 | func (p pngChunk) CRCIsValid() bool { 39 | return p.CRC == p.CalculateCRC() 40 | } 41 | 42 | func (p pngChunk) CalculateCRC() uint32 { 43 | crcTable := crc32.MakeTable(crc32.IEEE) 44 | 45 | return crc32.Checksum(p.Bytes(), crcTable) 46 | } 47 | 48 | func (p pngChunk) CRCOffset() int64 { 49 | return p.Offset + int64(8+p.Length) 50 | } 51 | 52 | func readPNGChunks(reader io.ReadSeeker) []pngChunk { 53 | chunks := []pngChunk{} 54 | 55 | reader.Seek(chunkStartOffset, os.SEEK_SET) 56 | 57 | readChunk := func() (*pngChunk, error) { 58 | var chunk pngChunk 59 | chunk.Offset, _ = reader.Seek(0, os.SEEK_CUR) 60 | 61 | binary.Read(reader, binary.BigEndian, &chunk.Length) 62 | 63 | chunk.Data = make([]byte, chunk.Length) 64 | 65 | err := binary.Read(reader, binary.BigEndian, &chunk.Type) 66 | if err != nil { 67 | goto read_error 68 | } 69 | 70 | if read, err := reader.Read(chunk.Data); read == 0 || err != nil { 71 | goto read_error 72 | } 73 | 74 | err = binary.Read(reader, binary.BigEndian, &chunk.CRC) 75 | if err != nil { 76 | goto read_error 77 | } 78 | 79 | return &chunk, nil 80 | 81 | read_error: 82 | return nil, fmt.Errorf("Read error") 83 | } 84 | 85 | chunk, err := readChunk() 86 | if err != nil { 87 | return chunks 88 | } 89 | 90 | chunks = append(chunks, *chunk) 91 | 92 | // Read the first chunk 93 | for string(chunks[len(chunks)-1].Type[:]) != endChunk { 94 | 95 | chunk, err := readChunk() 96 | if err != nil { 97 | break 98 | } 99 | 100 | chunks = append(chunks, *chunk) 101 | } 102 | 103 | return chunks 104 | } 105 | -------------------------------------------------------------------------------- /readme.go: -------------------------------------------------------------------------------- 1 | // exifremove clears exif information in jpg and png files 2 | 3 | package exifremove 4 | --------------------------------------------------------------------------------