Skip to content

Commit f7603e7

Browse files
forgejo-backport-actionmfenniak
authored andcommitted
[v13.0/forgejo] feat: strip EXIF information from uploaded avatars (go-gitea#9689)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/9638 Strips EXIF information from uploaded avatars (excluding the orientation tag), affecting both user & repo avatars. Adds a new subcommand `forgejo doctor 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/9689): <!--number 9689 --><!--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--> Co-authored-by: Mathieu Fenniak <[email protected]> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9689 Reviewed-by: Mathieu Fenniak <[email protected]> Co-authored-by: forgejo-backport-action <[email protected]> Co-committed-by: forgejo-backport-action <[email protected]>
1 parent 0fda75e commit f7603e7

File tree

9 files changed

+276
-13
lines changed

9 files changed

+276
-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: 90 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
}
@@ -98,6 +105,14 @@ You should back-up your database before doing this and ensure that your database
98105
}
99106
}
100107

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

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.18.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
@@ -249,6 +260,7 @@ require (
249260
golang.org/x/tools v0.36.0 // indirect
250261
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
251262
gopkg.in/warnings.v0 v0.1.2 // indirect
263+
gopkg.in/yaml.v2 v2.4.0 // indirect
252264
gopkg.in/yaml.v3 v3.0.1 // indirect
253265
)
254266

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.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
@@ -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=
@@ -722,7 +754,11 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
722754
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
723755
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
724756
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
757+
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
758+
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
759+
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
725760
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
761+
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
726762
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
727763
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
728764
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -921,8 +957,10 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
921957
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
922958
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
923959
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
960+
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
924961
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
925962
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
963+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
926964
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
927965
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
928966
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)