From 71a49e2b43218d34f97b2276c43bdeb2df4a53d2 Mon Sep 17 00:00:00 2001 From: Tobi Smethurst <31960611+tsmethurst@users.noreply.github.com> Date: Thu, 1 Apr 2021 20:46:45 +0200 Subject: Api/v1/accounts (#8) * start work on accounts module * plodding away on the accounts endpoint * groundwork for other account routes * add password validator * validation utils * require account approval flags * comments * comments * go fmt * comments * add distributor stub * rename api to federator * tidy a bit * validate new account requests * rename r router * comments * add domain blocks * add some more shortcuts * add some more shortcuts * check email + username availability * email block checking for signups * chunking away at it * tick off a few more things * some fiddling with tests * add mock package * relocate repo * move mocks around * set app id on new signups * initialize oauth server properly * rename oauth server * proper mocking tests * go fmt ./... * add required fields * change name of func * move validation to account.go * more tests! * add some file utility tools * add mediaconfig * new shortcut * add some more fields * add followrequest model * add notify * update mastotypes * mock out storage interface * start building media interface * start on update credentials * mess about with media a bit more * test image manipulation * media more or less working * account update nearly working * rearranging my package ;) ;) ;) * phew big stuff!!!! * fix type checking * *fiddles* * Add CreateTables func * account registration flow working * tidy * script to step through auth flow * add a lil helper for generating user uris * fiddling with federation a bit * update progress * Tidying and linting --- internal/media/util.go | 192 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 internal/media/util.go (limited to 'internal/media/util.go') diff --git a/internal/media/util.go b/internal/media/util.go new file mode 100644 index 000000000..9ffb79a46 --- /dev/null +++ b/internal/media/util.go @@ -0,0 +1,192 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package media + +import ( + "bytes" + "errors" + "fmt" + "image" + "image/gif" + "image/jpeg" + "image/png" + + "github.com/buckket/go-blurhash" + "github.com/h2non/filetype" + "github.com/nfnt/resize" + "github.com/superseriousbusiness/exifremove/pkg/exifremove" +) + +// parseContentType parses the MIME content type from a file, returning it as a string in the form (eg., "image/jpeg"). +// Returns an error if the content type is not something we can process. +func parseContentType(content []byte) (string, error) { + head := make([]byte, 261) + _, err := bytes.NewReader(content).Read(head) + if err != nil { + return "", fmt.Errorf("could not read first magic bytes of file: %s", err) + } + + kind, err := filetype.Match(head) + if err != nil { + return "", err + } + + if kind == filetype.Unknown { + return "", errors.New("filetype unknown") + } + + return kind.MIME.Value, nil +} + +// supportedImageType checks mime type of an image against a slice of accepted types, +// and returns True if the mime type is accepted. +func supportedImageType(mimeType string) bool { + acceptedImageTypes := []string{ + "image/jpeg", + "image/gif", + "image/png", + } + for _, accepted := range acceptedImageTypes { + if mimeType == accepted { + return true + } + } + return false +} + +// purgeExif is a little wrapper for the action of removing exif data from an image. +// Only pass pngs or jpegs to this function. +func purgeExif(b []byte) ([]byte, error) { + if len(b) == 0 { + return nil, errors.New("passed image was not valid") + } + + clean, err := exifremove.Remove(b) + if err != nil { + return nil, fmt.Errorf("could not purge exif from image: %s", err) + } + if len(clean) == 0 { + return nil, errors.New("purged image was not valid") + } + return clean, nil +} + +func deriveImage(b []byte, extension string) (*imageAndMeta, error) { + var i image.Image + var err error + + switch extension { + case "image/jpeg": + i, err = jpeg.Decode(bytes.NewReader(b)) + if err != nil { + return nil, err + } + case "image/png": + i, err = png.Decode(bytes.NewReader(b)) + if err != nil { + return nil, err + } + case "image/gif": + i, err = gif.Decode(bytes.NewReader(b)) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("extension %s not recognised", extension) + } + + width := i.Bounds().Size().X + height := i.Bounds().Size().Y + size := width * height + aspect := float64(width) / float64(height) + bh, err := blurhash.Encode(4, 3, i) + if err != nil { + return nil, fmt.Errorf("error generating blurhash: %s", err) + } + + out := &bytes.Buffer{} + if err := jpeg.Encode(out, i, nil); err != nil { + return nil, err + } + return &imageAndMeta{ + image: out.Bytes(), + width: width, + height: height, + size: size, + aspect: aspect, + blurhash: bh, + }, nil +} + +// deriveThumbnailFromImage returns a byte slice and metadata for a 256-pixel-width thumbnail +// of a given jpeg, png, or gif, or an error if something goes wrong. +// +// Note that the aspect ratio of the image will be retained, +// so it will not necessarily be a square. +func deriveThumbnail(b []byte, extension string) (*imageAndMeta, error) { + var i image.Image + var err error + + switch extension { + case "image/jpeg": + i, err = jpeg.Decode(bytes.NewReader(b)) + if err != nil { + return nil, err + } + case "image/png": + i, err = png.Decode(bytes.NewReader(b)) + if err != nil { + return nil, err + } + case "image/gif": + i, err = gif.Decode(bytes.NewReader(b)) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("extension %s not recognised", extension) + } + + thumb := resize.Thumbnail(256, 256, i, resize.NearestNeighbor) + width := thumb.Bounds().Size().X + height := thumb.Bounds().Size().Y + size := width * height + aspect := float64(width) / float64(height) + + out := &bytes.Buffer{} + if err := jpeg.Encode(out, thumb, nil); err != nil { + return nil, err + } + return &imageAndMeta{ + image: out.Bytes(), + width: width, + height: height, + size: size, + aspect: aspect, + }, nil +} + +type imageAndMeta struct { + image []byte + width int + height int + size int + aspect float64 + blurhash string +} -- cgit v1.2.3