From 32629a378dda47c1ed242a3cb45b98ca4dddf780 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Wed, 14 Apr 2021 18:16:58 +0200 Subject: [PATCH] work on emojis --- PROGRESS.md | 1 + internal/apimodule/admin/admin.go | 88 ++++++++++++ internal/apimodule/admin/emojicreate.go | 130 ++++++++++++++++++ internal/apimodule/fileserver/fileserver.go | 3 - internal/apimodule/fileserver/serveemoji.go | 7 - internal/apimodule/fileserver/servefile.go | 100 +++++++++++++- internal/apimodule/status/status.go | 14 +- internal/apimodule/status/statuscreate.go | 17 ++- .../apimodule/status/statuscreate_test.go | 41 ++++++ internal/db/gtsmodel/emoji.go | 6 +- internal/db/gtsmodel/status.go | 4 +- internal/distributor/distributor.go | 7 +- internal/gotosocial/actions.go | 19 ++- internal/mastotypes/converter.go | 16 +++ internal/mastotypes/mastomodel/emoji.go | 10 ++ internal/media/media.go | 21 ++- internal/media/media_test.go | 14 ++ internal/media/test/rainbow-original.png | Bin 0 -> 36702 bytes internal/media/test/rainbow-static.png | Bin 0 -> 4389 bytes internal/media/util.go | 1 + internal/util/parse.go | 6 +- internal/util/regexes.go | 36 +++++ internal/util/status.go | 13 -- internal/util/validation.go | 10 ++ testrig/db.go | 7 + testrig/distributor.go | 2 +- testrig/media/rainbow-original.png | Bin 0 -> 36702 bytes testrig/media/rainbow-static.png | Bin 0 -> 10413 bytes testrig/storage.go | 31 ++++- testrig/testmodels.go | 52 ++++++- testrig/util.go | 16 +-- 31 files changed, 605 insertions(+), 67 deletions(-) create mode 100644 internal/apimodule/admin/admin.go create mode 100644 internal/apimodule/admin/emojicreate.go delete mode 100644 internal/apimodule/fileserver/serveemoji.go create mode 100644 internal/media/test/rainbow-original.png create mode 100644 internal/media/test/rainbow-static.png create mode 100644 internal/util/regexes.go create mode 100755 testrig/media/rainbow-original.png create mode 100755 testrig/media/rainbow-static.png diff --git a/PROGRESS.md b/PROGRESS.md index 89ada4aa7..9a41805c8 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -144,6 +144,7 @@ * [ ] Custom Emojis * [ ] /api/v1/custom_emojis GET (Show this server's custom emoji) * [ ] Admin + * [x] /api/v1/admin/custom_emojis POST (Upload a custom emoji for instance-wide usage) * [ ] /api/v1/admin/accounts GET (View accounts filtered by criteria) * [ ] /api/v1/admin/accounts/:id GET (View admin level info about an account) * [ ] /api/v1/admin/accounts/:id/action POST (Perform an admin action on account) diff --git a/internal/apimodule/admin/admin.go b/internal/apimodule/admin/admin.go new file mode 100644 index 000000000..81d00116f --- /dev/null +++ b/internal/apimodule/admin/admin.go @@ -0,0 +1,88 @@ +/* + 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 admin + +import ( + "fmt" + "net/http" + + "github.com/sirupsen/logrus" + "github.com/superseriousbusiness/gotosocial/internal/apimodule" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/mastotypes" + "github.com/superseriousbusiness/gotosocial/internal/media" + "github.com/superseriousbusiness/gotosocial/internal/router" +) + +const ( + idKey = "id" + basePath = "/api/v1/admin" + emojiPath = basePath + "/custom_emojis" + basePathWithID = basePath + "/:" + idKey + verifyPath = basePath + "/verify_credentials" + updateCredentialsPath = basePath + "/update_credentials" +) + +type adminModule struct { + config *config.Config + db db.DB + mediaHandler media.MediaHandler + mastoConverter mastotypes.Converter + log *logrus.Logger +} + +// New returns a new account module +func New(config *config.Config, db db.DB, mediaHandler media.MediaHandler, mastoConverter mastotypes.Converter, log *logrus.Logger) apimodule.ClientAPIModule { + return &adminModule{ + config: config, + db: db, + mediaHandler: mediaHandler, + mastoConverter: mastoConverter, + log: log, + } +} + +// Route attaches all routes from this module to the given router +func (m *adminModule) Route(r router.Router) error { + r.AttachHandler(http.MethodPost, emojiPath, m.emojiCreatePOSTHandler) + return nil +} + +func (m *adminModule) CreateTables(db db.DB) error { + models := []interface{}{ + >smodel.User{}, + >smodel.Account{}, + >smodel.Follow{}, + >smodel.FollowRequest{}, + >smodel.Status{}, + >smodel.Application{}, + >smodel.EmailDomainBlock{}, + >smodel.MediaAttachment{}, + >smodel.Emoji{}, + } + + for _, m := range models { + if err := db.CreateTable(m); err != nil { + return fmt.Errorf("error creating table: %s", err) + } + } + return nil +} diff --git a/internal/apimodule/admin/emojicreate.go b/internal/apimodule/admin/emojicreate.go new file mode 100644 index 000000000..91457c07c --- /dev/null +++ b/internal/apimodule/admin/emojicreate.go @@ -0,0 +1,130 @@ +/* + 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 admin + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel" + "github.com/superseriousbusiness/gotosocial/internal/media" + "github.com/superseriousbusiness/gotosocial/internal/oauth" + "github.com/superseriousbusiness/gotosocial/internal/util" +) + +func (m *adminModule) emojiCreatePOSTHandler(c *gin.Context) { + l := m.log.WithFields(logrus.Fields{ + "func": "emojiCreatePOSTHandler", + "request_uri": c.Request.RequestURI, + "user_agent": c.Request.UserAgent(), + "origin_ip": c.ClientIP(), + }) + + // make sure we're authed with an admin account + authed, err := oauth.MustAuth(c, true, true, true, true) // posting a status is serious business so we want *everything* + if err != nil { + l.Debugf("couldn't auth: %s", err) + c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + return + } + if !authed.User.Admin { + l.Debugf("user %s not an admin", authed.User.ID) + c.JSON(http.StatusForbidden, gin.H{"error": "not an admin"}) + return + } + + // extract the media create form from the request context + l.Tracef("parsing request form: %+v", c.Request.Form) + form := &mastotypes.EmojiCreateRequest{} + if err := c.ShouldBind(form); err != nil { + l.Debugf("error parsing form %+v: %s", c.Request.Form, err) + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not parse form: %s", err)}) + return + } + + // Give the fields on the request form a first pass to make sure the request is superficially valid. + l.Tracef("validating form %+v", form) + if err := validateCreateEmoji(form); err != nil { + l.Debugf("error validating form: %s", err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // open the emoji and extract the bytes from it + f, err := form.Image.Open() + if err != nil { + l.Debugf("error opening emoji: %s", err) + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not open provided emoji: %s", err)}) + return + } + buf := new(bytes.Buffer) + size, err := io.Copy(buf, f) + if err != nil { + l.Debugf("error reading emoji: %s", err) + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not read provided emoji: %s", err)}) + return + } + if size == 0 { + l.Debug("could not read provided emoji: size 0 bytes") + c.JSON(http.StatusBadRequest, gin.H{"error": "could not read provided emoji: size 0 bytes"}) + return + } + + // allow the mediaHandler to work its magic of processing the emoji bytes, and putting them in whatever storage backend we're using + emoji, err := m.mediaHandler.ProcessLocalEmoji(buf.Bytes(), form.Shortcode) + if err != nil { + l.Debugf("error reading emoji: %s", err) + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("could not process emoji: %s", err)}) + return + } + + mastoEmoji, err := m.mastoConverter.EmojiToMasto(emoji) + if err != nil { + l.Debugf("error converting emoji to mastotype: %s", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("could not convert emoji: %s", err)}) + return + } + + if err := m.db.Put(emoji); err != nil { + l.Debugf("database error while processing emoji: %s", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("database error while processing emoji: %s", err)}) + return + } + + c.JSON(http.StatusOK, mastoEmoji) +} + +func validateCreateEmoji(form *mastotypes.EmojiCreateRequest) error { + // check there actually is an image attached and it's not size 0 + if form.Image == nil || form.Image.Size == 0 { + return errors.New("no emoji given") + } + + // a very superficial check to see if the media size limit is exceeded + if form.Image.Size > media.EmojiMaxBytes { + return fmt.Errorf("file size limit exceeded: limit is %d bytes but emoji was %d bytes", media.EmojiMaxBytes, form.Image.Size) + } + + return util.ValidateEmojiShortcode(form.Shortcode) +} diff --git a/internal/apimodule/fileserver/fileserver.go b/internal/apimodule/fileserver/fileserver.go index 825afecf4..e57e27627 100644 --- a/internal/apimodule/fileserver/fileserver.go +++ b/internal/apimodule/fileserver/fileserver.go @@ -36,9 +36,7 @@ const ( mediaTypeKey = "media_type" mediaSizeKey = "media_size" fileNameKey = "file_name" - shortcodeKey = "shortcode" - emojisPath = "emojis" filesPath = "files" ) @@ -66,7 +64,6 @@ func New(config *config.Config, db db.DB, storage storage.Storage, log *logrus.L // Route satisfies the RESTAPIModule interface func (m *fileServer) Route(s router.Router) error { s.AttachHandler(http.MethodGet, fmt.Sprintf("%s/:%s/:%s/:%s/:%s", m.storageBase, accountIDKey, mediaTypeKey, mediaSizeKey, fileNameKey), m.ServeFile) - s.AttachHandler(http.MethodGet, fmt.Sprintf("%s/:%s/:%s/:%s/:%s", m.storageBase, accountIDKey, mediaTypeKey, mediaSizeKey, fileNameKey), m.serveEmoji) return nil } diff --git a/internal/apimodule/fileserver/serveemoji.go b/internal/apimodule/fileserver/serveemoji.go deleted file mode 100644 index 062e59afe..000000000 --- a/internal/apimodule/fileserver/serveemoji.go +++ /dev/null @@ -1,7 +0,0 @@ -package fileserver - -import "github.com/gin-gonic/gin" - -func (m *fileServer) serveEmoji(c *gin.Context) { - -} diff --git a/internal/apimodule/fileserver/servefile.go b/internal/apimodule/fileserver/servefile.go index 5813f4b33..66bc18542 100644 --- a/internal/apimodule/fileserver/servefile.go +++ b/internal/apimodule/fileserver/servefile.go @@ -75,12 +75,24 @@ func (m *fileServer) ServeFile(c *gin.Context) { // Only serve media types that are defined in our internal media module switch mediaType { - case media.MediaHeader, media.MediaAvatar, media.MediaAttachment, media.MediaEmoji: - default: - l.Debugf("mediatype %s not recognized", mediaType) - c.String(http.StatusNotFound, "404 page not found") + case media.MediaHeader, media.MediaAvatar, media.MediaAttachment: + m.serveAttachment(c, accountID, mediaType, mediaSize, fileName) + return + case media.MediaEmoji: + m.serveEmoji(c, accountID, mediaType, mediaSize, fileName) return } + l.Debugf("mediatype %s not recognized", mediaType) + c.String(http.StatusNotFound, "404 page not found") +} + +func (m *fileServer) serveAttachment(c *gin.Context, accountID string, mediaType string, mediaSize string, fileName string) { + l := m.log.WithFields(logrus.Fields{ + "func": "serveAttachment", + "request_uri": c.Request.RequestURI, + "user_agent": c.Request.UserAgent(), + "origin_ip": c.ClientIP(), + }) // This corresponds to original-sized image as it was uploaded, small (which is the thumbnail), or static switch mediaSize { @@ -147,3 +159,83 @@ func (m *fileServer) ServeFile(c *gin.Context) { // finally we can return with all the information we derived above c.DataFromReader(http.StatusOK, int64(contentLength), contentType, bytes.NewReader(attachmentBytes), map[string]string{}) } + +func (m *fileServer) serveEmoji(c *gin.Context, accountID string, mediaType string, mediaSize string, fileName string) { + l := m.log.WithFields(logrus.Fields{ + "func": "serveEmoji", + "request_uri": c.Request.RequestURI, + "user_agent": c.Request.UserAgent(), + "origin_ip": c.ClientIP(), + }) + + // This corresponds to original-sized emoji as it was uploaded, or static + switch mediaSize { + case media.MediaOriginal, media.MediaStatic: + default: + l.Debugf("mediasize %s not recognized", mediaSize) + c.String(http.StatusNotFound, "404 page not found") + return + } + + // derive the media id and the file extension from the last part of the request + spl := strings.Split(fileName, ".") + if len(spl) != 2 { + l.Debugf("filename %s not parseable", fileName) + c.String(http.StatusNotFound, "404 page not found") + return + } + wantedEmojiID := spl[0] + fileExtension := spl[1] + if wantedEmojiID == "" || fileExtension == "" { + l.Debugf("filename %s not parseable", fileName) + c.String(http.StatusNotFound, "404 page not found") + return + } + + // now we know the attachment ID that the caller is asking for we can use it to pull the attachment out of the db + emoji := >smodel.Emoji{} + if err := m.db.GetByID(wantedEmojiID, emoji); err != nil { + l.Debugf("emoji with id %s not retrievable: %s", wantedEmojiID, err) + c.String(http.StatusNotFound, "404 page not found") + return + } + + // make sure the instance account id owns the requested emoji + instanceAccount := >smodel.Account{} + if err := m.db.GetWhere("username", m.config.Host, instanceAccount); err != nil { + l.Debugf("error fetching instance account: %s", err) + c.String(http.StatusNotFound, "404 page not found") + return + } + if accountID != instanceAccount.ID { + l.Debugf("account %s does not own emoji with id %s", accountID, wantedEmojiID) + c.String(http.StatusNotFound, "404 page not found") + return + } + + // now we can start preparing the response depending on whether we're serving a thumbnail or a larger attachment + var storagePath string + var contentType string + var contentLength int + switch mediaSize { + case media.MediaOriginal: + storagePath = emoji.ImagePath + contentType = emoji.ImageContentType + contentLength = emoji.ImageFileSize + case media.MediaStatic: + storagePath = emoji.ImageStaticPath + contentType = "image/png" + contentLength = emoji.ImageStaticFileSize + } + + // use the path listed on the emoji we pulled out of the database to retrieve the object from storage + emojiBytes, err := m.storage.RetrieveFileFrom(storagePath) + if err != nil { + l.Debugf("error retrieving emoji from storage: %s", err) + c.String(http.StatusNotFound, "404 page not found") + return + } + + // finally we can return with all the information we derived above + c.DataFromReader(http.StatusOK, int64(contentLength), contentType, bytes.NewReader(emojiBytes), map[string]string{}) +} diff --git a/internal/apimodule/status/status.go b/internal/apimodule/status/status.go index 02ae77f7c..123ca0a56 100644 --- a/internal/apimodule/status/status.go +++ b/internal/apimodule/status/status.go @@ -20,6 +20,7 @@ package status import ( "fmt" + "net/http" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/apimodule" @@ -75,7 +76,7 @@ func New(config *config.Config, db db.DB, oauthServer oauth.Server, mediaHandler // Route attaches all routes from this module to the given router func (m *statusModule) Route(r router.Router) error { - // r.AttachHandler(http.MethodPost, basePath, m.accountCreatePOSTHandler) + r.AttachHandler(http.MethodPost, basePath, m.statusCreatePOSTHandler) // r.AttachHandler(http.MethodGet, basePathWithID, m.muxHandler) return nil } @@ -102,3 +103,14 @@ func (m *statusModule) CreateTables(db db.DB) error { } return nil } + +// func (m *statusModule) muxHandler(c *gin.Context) { +// ru := c.Request.RequestURI +// if strings.HasPrefix(ru, verifyPath) { +// m.accountVerifyGETHandler(c) +// } else if strings.HasPrefix(ru, updateCredentialsPath) { +// m.accountUpdateCredentialsPATCHHandler(c) +// } else { +// m.accountGETHandler(c) +// } +// } diff --git a/internal/apimodule/status/statuscreate.go b/internal/apimodule/status/statuscreate.go index 17360b125..1ae3d7135 100644 --- a/internal/apimodule/status/statuscreate.go +++ b/internal/apimodule/status/statuscreate.go @@ -232,6 +232,16 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { return } + mastoEmojis := []mastotypes.Emoji{} + for _, gtse := range newStatus.GTSEmojis { + me, err := m.mastoConverter.EmojiToMasto(gtse) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + mastoEmojis = append(mastoEmojis, me) + } + mastoStatus := &mastotypes.Status{ ID: newStatus.ID, CreatedAt: newStatus.CreatedAt.Format(time.RFC3339), @@ -248,6 +258,8 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { Account: mastoAccount, MediaAttachments: mastoAttachments, Mentions: mastoMentions, + Tags: nil, + Emojis: mastoEmojis, Text: form.Status, } c.JSON(http.StatusOK, mastoStatus) @@ -320,12 +332,15 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis gtsmodel. // Advanced takes priority if it's set. // If it's not set, take whatever masto visibility is set. // If *that's* not set either, then just take the account default. + // If that's also not set, take the default for the whole instance. if form.VisibilityAdvanced != nil { gtsBasicVis = *form.VisibilityAdvanced } else if form.Visibility != "" { gtsBasicVis = util.ParseGTSVisFromMastoVis(form.Visibility) - } else { + } else if accountDefaultVis != "" { gtsBasicVis = accountDefaultVis + } else { + gtsBasicVis = gtsmodel.VisibilityDefault } switch gtsBasicVis { diff --git a/internal/apimodule/status/statuscreate_test.go b/internal/apimodule/status/statuscreate_test.go index c87ba9e36..03b3d2d33 100644 --- a/internal/apimodule/status/statuscreate_test.go +++ b/internal/apimodule/status/statuscreate_test.go @@ -161,6 +161,47 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() { assert.Equal(suite.T(), mastomodel.VisibilityPrivate, statusReply.Visibility) } +func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() { + + t := suite.testTokens["local_account_1"] + oauthToken := oauth.PGTokenToOauthToken(t) + + // setup + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) + ctx.Set(oauth.SessionAuthorizedToken, oauthToken) + ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) + ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) + ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting + ctx.Request.Form = url.Values{ + "status": {"here is a rainbow emoji a few times! :rainbow: :rainbow: :rainbow: \n here's an emoji that isn't in the db: :test_emoji: "}, + } + suite.statusModule.statusCreatePOSTHandler(ctx) + + suite.EqualValues(http.StatusOK, recorder.Code) + + result := recorder.Result() + defer result.Body.Close() + b, err := ioutil.ReadAll(result.Body) + assert.NoError(suite.T(), err) + + statusReply := &mastomodel.Status{} + err = json.Unmarshal(b, statusReply) + assert.NoError(suite.T(), err) + + assert.Equal(suite.T(), "", statusReply.SpoilerText) + assert.Equal(suite.T(), "here is a rainbow emoji a few times! :rainbow: :rainbow: :rainbow: \n here's an emoji that isn't in the db: :test_emoji: ", statusReply.Content) + + assert.Len(suite.T(), statusReply.Emojis, 1) + mastoEmoji := statusReply.Emojis[0] + gtsEmoji := testrig.NewTestEmojis()["rainbow"] + + assert.Equal(suite.T(), gtsEmoji.Shortcode, mastoEmoji.Shortcode) + assert.Equal(suite.T(), gtsEmoji.ImageURL, mastoEmoji.URL) + assert.Equal(suite.T(), gtsEmoji.ImageStaticURL, mastoEmoji.StaticURL) +} + // Try to reply to a status that doesn't exist func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() { t := suite.testTokens["local_account_1"] diff --git a/internal/db/gtsmodel/emoji.go b/internal/db/gtsmodel/emoji.go index fbd5aedf9..da1e2e02c 100644 --- a/internal/db/gtsmodel/emoji.go +++ b/internal/db/gtsmodel/emoji.go @@ -25,9 +25,9 @@ type Emoji struct { ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull"` // String shortcode for this emoji -- the part that's between colons. This should be lowercase a-z_ // eg., 'blob_hug' 'purple_heart' Must be unique with domain. - Shortcode string `pg:"notnull,unique:shortcodedomain"` - // Origin domain of this emoji, eg 'example.org', 'queer.party'. Null for local emojis. - Domain string `pg:",unique:shortcodedomain"` + Shortcode string `pg:",notnull,unique:shortcodedomain"` + // Origin domain of this emoji, eg 'example.org', 'queer.party'. empty string for local emojis. + Domain string `pg:",notnull,default:'',use_zero,unique:shortcodedomain"` // When was this emoji created. Must be unique with shortcode. CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` // When was this emoji updated diff --git a/internal/db/gtsmodel/status.go b/internal/db/gtsmodel/status.go index 43792e9f7..04ddc8f35 100644 --- a/internal/db/gtsmodel/status.go +++ b/internal/db/gtsmodel/status.go @@ -49,7 +49,7 @@ type Status struct { // cw string for this status ContentWarning string // visibility entry for this status - Visibility Visibility + Visibility Visibility `pg:",notnull"` // mark the status as sensitive? Sensitive bool // what language is this status written in? @@ -95,6 +95,8 @@ const ( VisibilityMutualsOnly Visibility = "mutuals_only" // This status is visible only to mentioned recipients VisibilityDirect Visibility = "direct" + // Default visibility to use when no other setting can be found + VisibilityDefault Visibility = "public" ) type VisibilityAdvanced struct { diff --git a/internal/distributor/distributor.go b/internal/distributor/distributor.go index 027b32279..74b69c5b0 100644 --- a/internal/distributor/distributor.go +++ b/internal/distributor/distributor.go @@ -19,7 +19,6 @@ package distributor import ( - "github.com/go-fed/activity/pub" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" ) @@ -43,7 +42,7 @@ type Distributor interface { // distributor just implements the Distributor interface type distributor struct { - federator pub.FederatingActor + // federator pub.FederatingActor fromClientAPI chan FromClientAPI toClientAPI chan ToClientAPI stop chan interface{} @@ -51,9 +50,9 @@ type distributor struct { } // New returns a new Distributor that uses the given federator and logger -func New(federator pub.FederatingActor, log *logrus.Logger) Distributor { +func New(log *logrus.Logger) Distributor { return &distributor{ - federator: federator, + // federator: federator, fromClientAPI: make(chan FromClientAPI, 100), toClientAPI: make(chan ToClientAPI, 100), stop: make(chan interface{}), diff --git a/internal/gotosocial/actions.go b/internal/gotosocial/actions.go index ee336a249..3f12d7b65 100644 --- a/internal/gotosocial/actions.go +++ b/internal/gotosocial/actions.go @@ -29,13 +29,16 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/action" "github.com/superseriousbusiness/gotosocial/internal/apimodule" "github.com/superseriousbusiness/gotosocial/internal/apimodule/account" + "github.com/superseriousbusiness/gotosocial/internal/apimodule/admin" "github.com/superseriousbusiness/gotosocial/internal/apimodule/app" "github.com/superseriousbusiness/gotosocial/internal/apimodule/auth" "github.com/superseriousbusiness/gotosocial/internal/apimodule/fileserver" mediaModule "github.com/superseriousbusiness/gotosocial/internal/apimodule/media" + "github.com/superseriousbusiness/gotosocial/internal/apimodule/status" "github.com/superseriousbusiness/gotosocial/internal/cache" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/distributor" "github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/mastotypes" "github.com/superseriousbusiness/gotosocial/internal/media" @@ -51,10 +54,6 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr return fmt.Errorf("error creating dbservice: %s", err) } - if err := dbService.CreateInstanceAccount(); err != nil { - return fmt.Errorf("error creating instance account: %s", err) - } - router, err := router.New(c, log) if err != nil { return fmt.Errorf("error creating router: %s", err) @@ -68,6 +67,10 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr // build backend handlers mediaHandler := media.New(c, dbService, storageBackend, log) oauthServer := oauth.New(dbService, log) + distributor := distributor.New(log) + if err := distributor.Start(); err != nil { + return fmt.Errorf("error starting distributor: %s", err) + } // build converters and util mastoConverter := mastotypes.New(c, dbService) @@ -78,6 +81,8 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr appsModule := app.New(oauthServer, dbService, mastoConverter, log) mm := mediaModule.New(dbService, mediaHandler, mastoConverter, c, log) fileServerModule := fileserver.New(c, dbService, storageBackend, log) + adminModule := admin.New(c, dbService, mediaHandler, mastoConverter, log) + statusModule := status.New(c, dbService, oauthServer, mediaHandler, mastoConverter, distributor, log) apiModules := []apimodule.ClientAPIModule{ authModule, // this one has to go first so the other modules use its middleware @@ -85,6 +90,8 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr appsModule, mm, fileServerModule, + adminModule, + statusModule, } for _, m := range apiModules { @@ -96,6 +103,10 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr } } + if err := dbService.CreateInstanceAccount(); err != nil { + return fmt.Errorf("error creating instance account: %s", err) + } + gts, err := New(dbService, &cache.MockCache{}, router, federation.New(dbService, log), c) if err != nil { return fmt.Errorf("error creating gotosocial service: %s", err) diff --git a/internal/mastotypes/converter.go b/internal/mastotypes/converter.go index 0e81f1a06..cec138058 100644 --- a/internal/mastotypes/converter.go +++ b/internal/mastotypes/converter.go @@ -52,9 +52,14 @@ type Converter interface { // fields sanitized so that it can be served to non-authorized accounts without revealing any private information. AppToMastoPublic(application *gtsmodel.Application) (*mastotypes.Application, error) + // AttachmentToMasto converts a gts model media attacahment into its mastodon representation for serialization on the API. AttachmentToMasto(attachment *gtsmodel.MediaAttachment) (mastotypes.Attachment, error) + // MentionToMasto converts a gts model mention into its mastodon (frontend) representation for serialization on the API. MentionToMasto(m *gtsmodel.Mention) (mastotypes.Mention, error) + + // EmojiToMasto converts a gts model emoji into its mastodon (frontend) representation for serialization on the API. + EmojiToMasto(e *gtsmodel.Emoji) (mastotypes.Emoji, error) } type converter struct { @@ -62,6 +67,7 @@ type converter struct { db db.DB } +// New returns a new Converter func New(config *config.Config, db db.DB) Converter { return &converter{ config: config, @@ -290,3 +296,13 @@ func (c *converter) MentionToMasto(m *gtsmodel.Mention) (mastotypes.Mention, err Acct: acct, }, nil } + +func (c *converter) EmojiToMasto(e *gtsmodel.Emoji) (mastotypes.Emoji, error) { + return mastotypes.Emoji{ + Shortcode: e.Shortcode, + URL: e.ImageURL, + StaticURL: e.ImageStaticURL, + VisibleInPicker: e.VisibleInPicker, + Category: e.CategoryID, + }, nil +} diff --git a/internal/mastotypes/mastomodel/emoji.go b/internal/mastotypes/mastomodel/emoji.go index e9ef95460..e9a0322bc 100644 --- a/internal/mastotypes/mastomodel/emoji.go +++ b/internal/mastotypes/mastomodel/emoji.go @@ -18,6 +18,8 @@ package mastotypes +import "mime/multipart" + // Emoji represents a custom emoji. See https://docs.joinmastodon.org/entities/emoji/ type Emoji struct { // REQUIRED @@ -36,3 +38,11 @@ type Emoji struct { // Used for sorting custom emoji in the picker. Category string `json:"category,omitempty"` } + +// EmojiCreateRequest represents a request to create a custom emoji made through the admin API. +type EmojiCreateRequest struct { + // Desired shortcode for the emoji, without surrounding colons. This must be unique for the domain. + Shortcode string `form:"shortcode" validation:"required"` + // Image file to use for the emoji. Must be png or gif and no larger than 50kb. + Image *multipart.FileHeader `form:"image" validation:"required"` +} diff --git a/internal/media/media.go b/internal/media/media.go index 28b58ba39..93a899e00 100644 --- a/internal/media/media.go +++ b/internal/media/media.go @@ -33,15 +33,23 @@ import ( ) const ( + // Key for small/thumbnail versions of media MediaSmall = "small" + // Key for original/fullsize versions of media and emoji MediaOriginal = "original" + // Key for static (non-animated) versions of emoji MediaStatic = "static" + // Key for media attachments MediaAttachment = "attachment" + // Key for profile header MediaHeader = "header" + // Key for profile avatar MediaAvatar = "avatar" + // Key for emoji type MediaEmoji = "emoji" - emojiMaxBytes = 51200 + // Maximum permitted bytes of an emoji upload (50kb) + EmojiMaxBytes = 51200 ) // MediaHandler provides an interface for parsing, storing, and retrieving media objects like photos, videos, and gifs. @@ -55,6 +63,11 @@ type MediaHandler interface { // puts it in whatever storage backend we're using, sets the relevant fields in the database for the new media, // and then returns information to the caller about the attachment. ProcessLocalAttachment(attachment []byte, accountID string) (*gtsmodel.MediaAttachment, error) + + // ProcessLocalEmoji takes a new emoji and a shortcode, cleans it up, puts it in storage, and creates a new + // *gts.Emoji for it, then returns it to the caller. It's the caller's responsibility to put the returned struct + // in the database. + ProcessLocalEmoji(emojiBytes []byte, shortcode string) (*gtsmodel.Emoji, error) } type mediaHandler struct { @@ -165,8 +178,8 @@ func (mh *mediaHandler) ProcessLocalEmoji(emojiBytes []byte, shortcode string) ( if len(emojiBytes) == 0 { return nil, errors.New("emoji was of size 0") } - if len(emojiBytes) > emojiMaxBytes { - return nil, fmt.Errorf("emoji size %d bytes exceeded max emoji size of %d bytes", len(emojiBytes), emojiMaxBytes) + if len(emojiBytes) > EmojiMaxBytes { + return nil, fmt.Errorf("emoji size %d bytes exceeded max emoji size of %d bytes", len(emojiBytes), EmojiMaxBytes) } // clean any exif data from image/png type but leave gifs alone @@ -227,7 +240,7 @@ func (mh *mediaHandler) ProcessLocalEmoji(emojiBytes []byte, shortcode string) ( } // store the static - if err := mh.storage.StoreFileAt(emojiPath, static.image); err != nil { + if err := mh.storage.StoreFileAt(emojiStaticPath, static.image); err != nil { return nil, fmt.Errorf("storage error: %s", err) } diff --git a/internal/media/media_test.go b/internal/media/media_test.go index 9022d2e20..75a544121 100644 --- a/internal/media/media_test.go +++ b/internal/media/media_test.go @@ -115,6 +115,11 @@ func (suite *MediaTestSuite) SetupTest() { logrus.Panicf("db connection error: %s", err) } } + + err := suite.db.CreateInstanceAccount() + if err != nil { + logrus.Panic(err) + } } // TearDownTest drops tables to make sure there's no data in the db @@ -151,6 +156,15 @@ func (suite *MediaTestSuite) TestSetHeaderOrAvatarForAccountID() { //TODO: add more checks here, cba right now! } +func (suite *MediaTestSuite) TestProcessLocalEmoji() { + f, err := ioutil.ReadFile("./test/rainbow-original.png") + assert.NoError(suite.T(), err) + + emoji, err := suite.mediaHandler.ProcessLocalEmoji(f, "rainbow") + assert.NoError(suite.T(), err) + suite.log.Debugf("%+v", emoji) +} + // TODO: add tests for sad path, gif, png.... func TestMediaTestSuite(t *testing.T) { diff --git a/internal/media/test/rainbow-original.png b/internal/media/test/rainbow-original.png new file mode 100644 index 0000000000000000000000000000000000000000..fdbfaeec3438c4a751749bfd65b5de10d7d20424 GIT binary patch literal 36702 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 literal 0 HcmV?d00001 diff --git a/internal/media/util.go b/internal/media/util.go index 6501a34b8..64d1ee770 100644 --- a/internal/media/util.go +++ b/internal/media/util.go @@ -248,6 +248,7 @@ func deriveThumbnail(b []byte, contentType string, x uint, y uint) (*imageAndMet }, nil } +// deriveStaticEmojji takes a given gif or png of an emoji, decodes it, and re-encodes it as a static png. func deriveStaticEmoji(b []byte, contentType string) (*imageAndMeta, error) { var i image.Image var err error diff --git a/internal/util/parse.go b/internal/util/parse.go index 9f3f7fad5..f0bcff5dc 100644 --- a/internal/util/parse.go +++ b/internal/util/parse.go @@ -25,6 +25,7 @@ import ( mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel" ) +// URIs contains a bunch of URIs and URLs for a user, host, account, etc. type URIs struct { HostURL string UserURL string @@ -38,6 +39,7 @@ type URIs struct { CollectionURI string } +// GenerateURIs throws together a bunch of URIs for the given username, with the given protocol and host. func GenerateURIs(username string, protocol string, host string) *URIs { hostURL := fmt.Sprintf("%s://%s", protocol, host) userURL := fmt.Sprintf("%s/@%s", hostURL, username) @@ -74,8 +76,6 @@ func ParseGTSVisFromMastoVis(m mastotypes.Visibility) gtsmodel.Visibility { return gtsmodel.VisibilityFollowersOnly case mastotypes.VisibilityDirect: return gtsmodel.VisibilityDirect - default: - break } return "" } @@ -91,8 +91,6 @@ func ParseMastoVisFromGTSVis(m gtsmodel.Visibility) mastotypes.Visibility { return mastotypes.VisibilityPrivate case gtsmodel.VisibilityDirect: return mastotypes.VisibilityDirect - default: - break } return "" } diff --git a/internal/util/regexes.go b/internal/util/regexes.go new file mode 100644 index 000000000..60b397d86 --- /dev/null +++ b/internal/util/regexes.go @@ -0,0 +1,36 @@ +/* + 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 util + +import "regexp" + +var ( + // mention regex can be played around with here: https://regex101.com/r/qwM9D3/1 + mentionRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?: |\n)` + mentionRegex = regexp.MustCompile(mentionRegexString) + // hashtag regex can be played with here: https://regex101.com/r/Vhy8pg/1 + hashtagRegexString = `(?: |^|\W)?#([a-zA-Z0-9]{1,30})(?:\b|\r)` + hashtagRegex = regexp.MustCompile(hashtagRegexString) + // emoji regex can be played with here: https://regex101.com/r/478XGM/1 + emojiRegexString = `(?: |^|\W)?:([a-zA-Z0-9_]{2,30}):(?:\b|\r)?` + emojiRegex = regexp.MustCompile(emojiRegexString) + // emoji shortcode regex can be played with here: https://regex101.com/r/zMDRaG/1 + emojiShortcodeString = `^[a-z0-9_]{2,30}$` + emojiShortcodeRegex = regexp.MustCompile(emojiShortcodeString) +) diff --git a/internal/util/status.go b/internal/util/status.go index 7e4e669bf..e4b3ec6a5 100644 --- a/internal/util/status.go +++ b/internal/util/status.go @@ -19,22 +19,9 @@ package util import ( - "regexp" "strings" ) -var ( - // mention regex can be played around with here: https://regex101.com/r/qwM9D3/1 - mentionRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?: |\n)` - mentionRegex = regexp.MustCompile(mentionRegexString) - // hashtag regex can be played with here: https://regex101.com/r/Vhy8pg/1 - hashtagRegexString = `(?: |^|\W)?#([a-zA-Z0-9]{1,30})(?:\b|\r)` - hashtagRegex = regexp.MustCompile(hashtagRegexString) - // emoji regex can be played with here: https://regex101.com/r/478XGM/1 - emojiRegexString = `(?: |^|\W)?:([a-zA-Z0-9_]{2,30}):(?:\b|\r)?` - emojiRegex = regexp.MustCompile(emojiRegexString) -) - // DeriveMentions takes a plaintext (ie., not html-formatted) status, // and applies a regex to it to return a deduplicated list of accounts // mentioned in that status. diff --git a/internal/util/validation.go b/internal/util/validation.go index 88a56875c..8102bc35d 100644 --- a/internal/util/validation.go +++ b/internal/util/validation.go @@ -142,3 +142,13 @@ func ValidatePrivacy(privacy string) error { // TODO: add some validation logic here -- length, characters, etc return nil } + +// ValidateEmojiShortcode just runs the given shortcode through the regular expression +// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters, +// lowercase a-z, numbers, and underscores. +func ValidateEmojiShortcode(shortcode string) error { + if !emojiShortcodeRegex.MatchString(shortcode) { + return fmt.Errorf("shortcode %s did not pass validation, must be between 2 and 30 characters, lowercase letters, numbers, and underscores only", shortcode) + } + return nil +} diff --git a/testrig/db.go b/testrig/db.go index 4d5bb6f18..260020c50 100644 --- a/testrig/db.go +++ b/testrig/db.go @@ -40,6 +40,7 @@ var testModels []interface{} = []interface{}{ >smodel.Status{}, >smodel.Tag{}, >smodel.User{}, + >smodel.Emoji{}, &oauth.Token{}, &oauth.Client{}, } @@ -106,6 +107,12 @@ func StandardDBSetup(db db.DB) { } } + for _, v := range NewTestEmojis() { + if err := db.Put(v); err != nil { + panic(err) + } + } + if err := db.CreateInstanceAccount(); err != nil { panic(err) } diff --git a/testrig/distributor.go b/testrig/distributor.go index c37f3dd26..e21321d53 100644 --- a/testrig/distributor.go +++ b/testrig/distributor.go @@ -21,5 +21,5 @@ package testrig import "github.com/superseriousbusiness/gotosocial/internal/distributor" func NewTestDistributor() distributor.Distributor { - return distributor.New(nil, NewTestLog()) + return distributor.New(NewTestLog()) } diff --git a/testrig/media/rainbow-original.png b/testrig/media/rainbow-original.png new file mode 100755 index 0000000000000000000000000000000000000000..fdbfaeec3438c4a751749bfd65b5de10d7d20424 GIT binary patch literal 36702 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 Xctx3tl*O3;00000NkvXXu0mjfxFktP literal 0 HcmV?d00001 diff --git a/testrig/storage.go b/testrig/storage.go index c4ea9a951..3b520364b 100644 --- a/testrig/storage.go +++ b/testrig/storage.go @@ -36,9 +36,9 @@ func NewTestStorage() storage.Storage { // StandardStorageSetup populates the storage with standard test entries from the given directory. func StandardStorageSetup(s storage.Storage, relativePath string) { - stored := NewTestStored() + storedA := NewTestStoredAttachments() a := NewTestAttachments() - for k, paths := range stored { + for k, paths := range storedA { attachmentInfo, ok := a[k] if !ok { panic(fmt.Errorf("key %s not found in test attachments", k)) @@ -62,6 +62,33 @@ func StandardStorageSetup(s storage.Storage, relativePath string) { panic(err) } } + + storedE := NewTestStoredEmoji() + e := NewTestEmojis() + for k, paths := range storedE { + emojiInfo, ok := e[k] + if !ok { + panic(fmt.Errorf("key %s not found in test emojis", k)) + } + filenameOriginal := paths.original + filenameStatic := paths.static + pathOriginal := emojiInfo.ImagePath + pathStatic := emojiInfo.ImageStaticPath + bOriginal, err := os.ReadFile(fmt.Sprintf("%s/%s", relativePath, filenameOriginal)) + if err != nil { + panic(err) + } + if err := s.StoreFileAt(pathOriginal, bOriginal); err != nil { + panic(err) + } + bStatic, err := os.ReadFile(fmt.Sprintf("%s/%s", relativePath, filenameStatic)) + if err != nil { + panic(err) + } + if err := s.StoreFileAt(pathStatic, bStatic); err != nil { + panic(err) + } + } } // StandardStorageTeardown deletes everything in storage so that it's clean for the next test diff --git a/testrig/testmodels.go b/testrig/testmodels.go index 12c9f519a..dde38912c 100644 --- a/testrig/testmodels.go +++ b/testrig/testmodels.go @@ -206,6 +206,10 @@ func NewTestUsers() map[string]*gtsmodel.User { // NewTestAccounts returns a map of accounts keyed by what type of account they are. func NewTestAccounts() map[string]*gtsmodel.Account { accounts := map[string]*gtsmodel.Account{ + "instance_account": { + ID: "39b745a3-774d-4b65-8bb2-b63d9e20a343", + Username: "localhost:8080", + }, "unconfirmed_account": { ID: "59e197f5-87cd-4be8-ac7c-09082ccc4b4d", Username: "weed_lord420", @@ -610,14 +614,41 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment { } } -type paths struct { - original string - small string +func NewTestEmojis() map[string]*gtsmodel.Emoji { + return map[string]*gtsmodel.Emoji{ + "rainbow": { + ID: "a96ec4f3-1cae-47e4-a508-f9d66a6b221b", + Shortcode: "rainbow", + Domain: "", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + ImageRemoteURL: "", + ImageStaticRemoteURL: "", + ImageURL: "http://localhost:8080/fileserver/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/original/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png", + ImagePath: "/tmp/gotosocial/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/original/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png", + ImageStaticURL: "http://localhost:8080/fileserver/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/static/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png", + ImageStaticPath: "/tmp/gotosocial/39b745a3-774d-4b65-8bb2-b63d9e20a343/emoji/static/a96ec4f3-1cae-47e4-a508-f9d66a6b221b.png", + ImageContentType: "image/png", + ImageFileSize: 36702, + ImageStaticFileSize: 10413, + ImageUpdatedAt: time.Now(), + Disabled: false, + URI: "http://localhost:8080/emoji/a96ec4f3-1cae-47e4-a508-f9d66a6b221b", + VisibleInPicker: true, + CategoryID: "", + }, + } } -// NewTestStored returns a map of filenames, keyed according to which attachment they pertain to. -func NewTestStored() map[string]paths { - return map[string]paths{ +type filenames struct { + original string + small string + static string +} + +// NewTestStoredAttachments returns a map of filenames, keyed according to which attachment they pertain to. +func NewTestStoredAttachments() map[string]filenames { + return map[string]filenames{ "admin_account_status_1_attachment_1": { original: "welcome-original.jpeg", small: "welcome-small.jpeg", @@ -633,6 +664,15 @@ func NewTestStored() map[string]paths { } } +func NewTestStoredEmoji() map[string]filenames { + return map[string]filenames{ + "rainbow": { + original: "rainbow-original.png", + static: "rainbow-static.png", + }, + } +} + // NewTestStatuses returns a map of statuses keyed according to which account // and status they are. func NewTestStatuses() map[string]*gtsmodel.Status { diff --git a/testrig/util.go b/testrig/util.go index e6342d93d..96a979342 100644 --- a/testrig/util.go +++ b/testrig/util.go @@ -47,15 +47,13 @@ func CreateMultipartFormData(fieldName string, fileName string, extraFields map[ return b, nil, err } - if extraFields != nil { - for k, v := range extraFields { - f, err := w.CreateFormField(k) - if err != nil { - return b, nil, err - } - if _, err := io.Copy(f, bytes.NewBufferString(v)); err != nil { - return b, nil, err - } + for k, v := range extraFields { + f, err := w.CreateFormField(k) + if err != nil { + return b, nil, err + } + if _, err := io.Copy(f, bytes.NewBufferString(v)); err != nil { + return b, nil, err } }