Skip to content

Commit 78d92aa

Browse files
mfenniakMathieu Fenniak
authored andcommitted
feat: strip EXIF information from uploaded avatars (go-gitea#9638)
Strips EXIF information from uploaded avatars (excluding the orientation tag), affecting both user & repo avatars. Adds a new subcommand `forgejo admin avatar-strip-exif` to perform a retroactive update of avatar files. Fixes go-gitea#9608. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [ ] I want the title to show in the release notes with a link to this pull request. - [x] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Features - [PR](https://codeberg.org/forgejo/forgejo/pulls/9638): <!--number 9638 --><!--line 0 --><!--description VXBsb2FkZWQgYXZhdGFyIGltYWdlcyBjYW4gc29tZXRpbWVzIGNvbnRhaW4gdW5leHBlY3RlZCBtZXRhZGF0YSBzdWNoIGFzIHRoZSBsb2NhdGlvbiB3aGVyZSB0aGUgaW1hZ2Ugd2FzIGNyZWF0ZWQsIG9yIHRoZSBkZXZpY2UgdGhlIGltYWdlIHdhcyBjcmVhdGVkIHdpdGgsIHN0b3JlZCBpbiBhIGZvcm1hdCBjYWxsZWQgRVhJRi4gRm9yZ2VqbyBub3cgcmVtb3ZlcyBFWElGIGRhdGEgd2hlbiBjdXN0b20gdXNlciBhbmQgcmVwb3NpdG9yeSBpbWFnZXMgYXJlIHVwbG9hZGVkIGluIG9yZGVyIHRvIHJlZHVjZSB0aGUgcmlzayBvZiBwZXJzb25hbGx5IGlkZW50aWZpYWJsZSBpbmZvcm1hdGlvbiBiZWluZyBsZWFrZWQgdW5leHBlY3RlZGx5LiBBIG5ldyBDTEkgc3ViY29tbWFuZCBgZm9yZ2VqbyBkb2N0b3IgYXZhdGFyLXN0cmlwLWV4aWZgIGNhbiBiZSB1c2VkIHRvIHN0cmlwIEVYSUYgaW5mb3JtYXRpb24gZnJvbSBhbGwgZXhpc3RpbmcgYXZhdGFyczsgd2UgcmVjb21tZW5kIHRoYXQgYWRtaW5pc3RyYXRvcnMgcnVuIHRoaXMgY29tbWFuZCBvbmNlIGFmdGVyIHVwZ3JhZGUgaW4gb3JkZXIgdG8gbWluaW1pemUgdGhpcyByaXNrIGZvciBleGlzdGluZyBzdG9yZWQgZmlsZXMu-->Uploaded avatar images can sometimes contain unexpected metadata such as the location where the image was created, or the device the image was created with, stored in a format called EXIF. Forgejo now removes EXIF data when custom user and repository images are uploaded in order to reduce the risk of personally identifiable information being leaked unexpectedly. A new CLI subcommand `forgejo doctor avatar-strip-exif` can be used to strip EXIF information from all existing avatars; we recommend that administrators run this command once after upgrade in order to minimize this risk for existing stored files.<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9638 Reviewed-by: Gusted <[email protected]> Co-authored-by: Mathieu Fenniak <[email protected]> Co-committed-by: Mathieu Fenniak <[email protected]>
1 parent 8eb8f49 commit 78d92aa

File tree

9 files changed

+277
-13
lines changed

9 files changed

+277
-13
lines changed

assets/go-licenses.json

Lines changed: 55 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/doctor.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package cmd
66
import (
77
"context"
88
"fmt"
9+
"image"
10+
"io"
911
golog "log"
1012
"os"
1113
"path/filepath"
@@ -15,11 +17,15 @@ import (
1517
"forgejo.org/models/db"
1618
"forgejo.org/models/migrations"
1719
migrate_base "forgejo.org/models/migrations/base"
20+
repo_model "forgejo.org/models/repo"
21+
user_model "forgejo.org/models/user"
1822
"forgejo.org/modules/container"
1923
"forgejo.org/modules/log"
2024
"forgejo.org/modules/setting"
25+
"forgejo.org/modules/storage"
2126
"forgejo.org/services/doctor"
2227

28+
exif_terminator "code.superseriousbusiness.org/exif-terminator"
2329
"github.com/urfave/cli/v3"
2430
)
2531

@@ -34,6 +40,7 @@ func cmdDoctor() *cli.Command {
3440
cmdDoctorCheck(),
3541
cmdRecreateTable(),
3642
cmdDoctorConvert(),
43+
cmdAvatarStripExif(),
3744
},
3845
}
3946
}
@@ -99,6 +106,15 @@ You should back-up your database before doing this and ensure that your database
99106
}
100107
}
101108

109+
func cmdAvatarStripExif() *cli.Command {
110+
return &cli.Command{
111+
Name: "avatar-strip-exif",
112+
Usage: "Strip EXIF metadata from all images in the avatar storage",
113+
Before: noDanglingArgs,
114+
Action: runAvatarStripExif,
115+
}
116+
}
117+
102118
func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error {
103119
stdCtx, cancel := installSignals(stdCtx)
104120
defer cancel()
@@ -231,3 +247,78 @@ func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error {
231247
}
232248
return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks)
233249
}
250+
251+
func runAvatarStripExif(ctx context.Context, c *cli.Command) error {
252+
ctx, cancel := installSignals(ctx)
253+
defer cancel()
254+
255+
if err := initDB(ctx); err != nil {
256+
return err
257+
}
258+
if err := storage.Init(); err != nil {
259+
return err
260+
}
261+
262+
type HasCustomAvatarRelativePath interface {
263+
CustomAvatarRelativePath() string
264+
}
265+
266+
doExifStrip := func(obj HasCustomAvatarRelativePath, name string, target_storage storage.ObjectStorage) error {
267+
if obj.CustomAvatarRelativePath() == "" {
268+
return nil
269+
}
270+
271+
log.Info("Stripping avatar for %s...", name)
272+
273+
avatarFile, err := target_storage.Open(obj.CustomAvatarRelativePath())
274+
if err != nil {
275+
return fmt.Errorf("storage.Avatars.Open: %w", err)
276+
}
277+
_, imgType, err := image.DecodeConfig(avatarFile)
278+
if err != nil {
279+
return fmt.Errorf("image.DecodeConfig: %w", err)
280+
}
281+
282+
// reset io.Reader for exif termination scan
283+
_, err = avatarFile.Seek(0, io.SeekStart)
284+
if err != nil {
285+
return fmt.Errorf("avatarFile.Seek: %w", err)
286+
}
287+
288+
cleanedData, err := exif_terminator.Terminate(avatarFile, imgType)
289+
if err != nil && strings.Contains(err.Error(), "cannot be processed") {
290+
// expected error for an image type that isn't supported by exif_terminator
291+
log.Info("... image type %s is not supported by exif_terminator, skipping.", imgType)
292+
return nil
293+
} else if err != nil {
294+
return fmt.Errorf("error cleaning exif data: %w", err)
295+
}
296+
297+
if err := storage.SaveFrom(target_storage, obj.CustomAvatarRelativePath(), func(w io.Writer) error {
298+
_, err := io.Copy(w, cleanedData)
299+
return err
300+
}); err != nil {
301+
return fmt.Errorf("Failed to create dir %s: %w", obj.CustomAvatarRelativePath(), err)
302+
}
303+
304+
log.Info("... completed %s.", name)
305+
306+
return nil
307+
}
308+
309+
err := db.Iterate(ctx, nil, func(ctx context.Context, user *user_model.User) error {
310+
return doExifStrip(user, fmt.Sprintf("user %s", user.Name), storage.Avatars)
311+
})
312+
if err != nil {
313+
return err
314+
}
315+
316+
err = db.Iterate(ctx, nil, func(ctx context.Context, repo *repo_model.Repository) error {
317+
return doExifStrip(repo, fmt.Sprintf("repo %s", repo.Name), storage.RepoAvatars)
318+
})
319+
if err != nil {
320+
return err
321+
}
322+
323+
return nil
324+
}

go.mod

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ require (
1717
code.forgejo.org/go-chi/session v1.0.2
1818
code.gitea.io/actions-proto-go v0.4.0
1919
code.gitea.io/sdk/gitea v0.21.0
20+
code.superseriousbusiness.org/exif-terminator v0.11.0
21+
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0
2022
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
2123
connectrpc.com/connect v1.19.1
2224
github.com/42wim/httpsig v1.2.3
@@ -34,6 +36,7 @@ require (
3436
github.com/djherbis/buffer v1.2.0
3537
github.com/djherbis/nio/v3 v3.0.1
3638
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
39+
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b
3740
github.com/dustin/go-humanize v1.0.1
3841
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
3942
github.com/emersion/go-imap v1.2.1
@@ -116,6 +119,7 @@ require (
116119

117120
require (
118121
cloud.google.com/go/compute/metadata v0.6.0 // indirect
122+
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 // indirect
119123
dario.cat/mergo v1.0.2 // indirect
120124
filippo.io/edwards25519 v1.1.0 // indirect
121125
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
@@ -160,13 +164,18 @@ require (
160164
github.com/davidmz/go-pageant v1.0.2 // indirect
161165
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
162166
github.com/dlclark/regexp2 v1.11.5 // indirect
167+
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect
168+
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
169+
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c // indirect
170+
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e // indirect
163171
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
164172
github.com/emirpasic/gods v1.18.1 // indirect
165173
github.com/fatih/color v1.18.0 // indirect
166174
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
167175
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
168176
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
169177
github.com/go-enry/go-oniguruma v1.2.1 // indirect
178+
github.com/go-errors/errors v1.1.1 // indirect
170179
github.com/go-fed/httpsig v1.1.0 // indirect
171180
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
172181
github.com/go-git/go-billy/v5 v5.6.2 // indirect
@@ -176,8 +185,10 @@ require (
176185
github.com/go-openapi/jsonreference v0.21.0 // indirect
177186
github.com/go-openapi/swag v0.23.1 // indirect
178187
github.com/go-webauthn/x v0.1.25 // indirect
188+
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
179189
github.com/goccy/go-json v0.10.5 // indirect
180190
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
191+
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d // indirect
181192
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
182193
github.com/golang/protobuf v1.5.4 // indirect
183194
github.com/golang/snappy v0.0.4 // indirect
@@ -250,6 +261,7 @@ require (
250261
golang.org/x/tools v0.37.0 // indirect
251262
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
252263
gopkg.in/warnings.v0 v0.1.2 // indirect
264+
gopkg.in/yaml.v2 v2.4.0 // indirect
253265
gopkg.in/yaml.v3 v3.0.1 // indirect
254266
)
255267

go.sum

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zC
4646
code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
4747
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
4848
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
49+
code.superseriousbusiness.org/exif-terminator v0.11.0 h1:Hof0MCcsa+1fS17gf86fTTZ8AQnMY9h9kzcc+2C6mVg=
50+
code.superseriousbusiness.org/exif-terminator v0.11.0/go.mod h1:9sutT1axa/kSdlPLlRFjCNKmyo/KNx8eX3XZvWBlAEY=
51+
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 h1:r9uq8StaSHYKJ8DklR9Xy+E9c40G1Z8yj5TRGi8L6+4=
52+
code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0/go.mod h1:IK1OlR6APjVB3E9tuYGvf0qXMrwP+TrzcHS5rf4wffQ=
53+
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 h1:I512jiIeXDC4//2BeSPrRM2ZS4wpBKUaPeTPxakMNGA=
54+
code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0/go.mod h1:SNHomXNW88o1pFfLHpD4KsCZLfcr4z5dm+xcX5SV10A=
4955
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY=
5056
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM=
5157
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
@@ -209,6 +215,22 @@ github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn
209215
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
210216
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
211217
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
218+
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
219+
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
220+
github.com/dsoprea/go-exif/v3 v3.0.0-20210428042052-dca55bf8ca15/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
221+
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b h1:NgNuLvW/gAFKU30ULWW0gtkCt56JfB7FrZ2zyo0wT8I=
222+
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
223+
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb h1:gwjJjUr6FY7zAWVEueFPrcRHhd9+IK81TcItbqw2du4=
224+
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM=
225+
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
226+
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
227+
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg=
228+
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
229+
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c h1:7j5aWACOzROpr+dvMtu8GnI97g9ShLWD72XIELMgn+c=
230+
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E=
231+
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
232+
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e h1:IxIbA7VbCNrwumIYjDoMOdf4KOSkMC6NJE4s8oRbE7E=
233+
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
212234
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
213235
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
214236
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 h1:XVUp6qW3BIkmM3/1EkrHpa6bL56APOynfXcZEmIgOhs=
@@ -258,6 +280,10 @@ github.com/go-enry/go-enry/v2 v2.9.2 h1:giOQAtCgBX08kosrX818DCQJTCNtKwoPBGu0qb6n
258280
github.com/go-enry/go-enry/v2 v2.9.2/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8=
259281
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
260282
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
283+
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
284+
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
285+
github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg=
286+
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
261287
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
262288
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
263289
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -291,6 +317,8 @@ github.com/go-webauthn/webauthn v0.14.0 h1:ZLNPUgPcDlAeoxe+5umWG/tEeCoQIDr7gE2Zx
291317
github.com/go-webauthn/webauthn v0.14.0/go.mod h1:QZzPFH3LJ48u5uEPAu+8/nWJImoLBWM7iAH/kSVSo6k=
292318
github.com/go-webauthn/x v0.1.25 h1:g/0noooIGcz/yCVqebcFgNnGIgBlJIccS+LYAa+0Z88=
293319
github.com/go-webauthn/x v0.1.25/go.mod h1:ieblaPY1/BVCV0oQTsA/VAo08/TWayQuJuo5Q+XxmTY=
320+
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
321+
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
294322
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
295323
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
296324
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
@@ -308,6 +336,9 @@ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9v
308336
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
309337
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
310338
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
339+
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
340+
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc=
341+
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
311342
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
312343
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
313344
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -404,6 +435,7 @@ github.com/inbucket/html2text v0.9.0 h1:ULJmVcBEMAcmLE+/rN815KG1Fx6+a4HhbUxiDiN+
404435
github.com/inbucket/html2text v0.9.0/go.mod h1:QDaumzl+/OzlSVbNohhmg+yAy5pKjUjzCKW2BMvztKE=
405436
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
406437
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
438+
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
407439
github.com/jhillyerd/enmime/v2 v2.2.0 h1:Pe35MB96eZK5Q0XjlvPftOgWypQpd1gcbfJKAt7rsB8=
408440
github.com/jhillyerd/enmime/v2 v2.2.0/go.mod h1:SOBXlCemjhiV2DvHhAKnJiWrtJGS/Ffuw4Iy7NjBTaI=
409441
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -724,7 +756,11 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
724756
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
725757
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
726758
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
759+
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
760+
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
761+
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
727762
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
763+
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
728764
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
729765
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
730766
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -923,8 +959,10 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
923959
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
924960
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
925961
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
962+
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
926963
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
927964
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
965+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
928966
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
929967
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
930968
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

modules/avatar/avatar.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ import (
1010
"image"
1111
"image/color"
1212
"image/png"
13+
"io"
1314

1415
_ "image/gif" // for processing gif images
1516
_ "image/jpeg" // for processing jpeg images
1617

1718
"forgejo.org/modules/avatar/identicon"
1819
"forgejo.org/modules/setting"
1920

21+
exif_terminator "code.superseriousbusiness.org/exif-terminator"
2022
"golang.org/x/image/draw"
2123

2224
_ "golang.org/x/image/webp" // for processing webp images
@@ -66,15 +68,29 @@ func processAvatarImage(data []byte, maxOriginSize int64) ([]byte, error) {
6668
return nil, fmt.Errorf("image height is too large: %d > %d", imgCfg.Height, setting.Avatar.MaxHeight)
6769
}
6870

71+
var cleanedBytes []byte
72+
if imgType != "gif" { // "gif" is the only imgType supported above, but not supported by exif_terminator
73+
cleanedData, err := exif_terminator.Terminate(bytes.NewReader(data), imgType)
74+
if err != nil {
75+
return nil, fmt.Errorf("error cleaning exif data: %w", err)
76+
}
77+
cleanedBytes, err = io.ReadAll(cleanedData)
78+
if err != nil {
79+
return nil, fmt.Errorf("error reading cleaned data: %w", err)
80+
}
81+
} else { // gif
82+
cleanedBytes = data
83+
}
84+
6985
// If the origin is small enough, just use it, then APNG could be supported,
7086
// otherwise, if the image is processed later, APNG loses animation.
7187
// And one more thing, webp is not fully supported, for animated webp, image.DecodeConfig works but Decode fails.
7288
// So for animated webp, if the uploaded file is smaller than maxOriginSize, it will be used, if it's larger, there will be an error.
7389
if len(data) < int(maxOriginSize) {
74-
return data, nil
90+
return cleanedBytes, nil
7591
}
7692

77-
img, _, err := image.Decode(bytes.NewReader(data))
93+
img, _, err := image.Decode(bytes.NewReader(cleanedBytes))
7894
if err != nil {
7995
return nil, fmt.Errorf("image.Decode: %w", err)
8096
}
@@ -94,7 +110,7 @@ func processAvatarImage(data []byte, maxOriginSize int64) ([]byte, error) {
94110

95111
// usually the png compression is not good enough, use the original image (no cropping/resizing) if the origin is smaller
96112
if len(data) <= len(resized) {
97-
return data, nil
113+
return cleanedBytes, nil
98114
}
99115

100116
return resized, nil

0 commit comments

Comments
 (0)