| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // 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 <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | package media | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/media" | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/regexes" | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/storage" | 
					
						
							| 
									
										
										
										
											2022-10-13 15:16:24 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/uris" | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | // GetFile retrieves a file from storage and streams it back | 
					
						
							|  |  |  | // to the caller via an io.reader embedded in *apimodel.Content. | 
					
						
							|  |  |  | func (p *Processor) GetFile( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	requester *gtsmodel.Account, | 
					
						
							| 
									
										
										
										
											2023-11-10 19:29:26 +01:00
										 |  |  | 	form *apimodel.GetContentRequestForm, | 
					
						
							|  |  |  | ) (*apimodel.Content, gtserror.WithCode) { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// Parse media size (small, static, original). | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	mediaSize, err := parseSize(form.MediaSize) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		err := gtserror.Newf("media size %s not valid", form.MediaSize) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// Parse media type (emoji, header, avatar, attachment). | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	mediaType, err := parseType(form.MediaType) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		err := gtserror.Newf("media type %s not valid", form.MediaType) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// Parse media ID from file name. | 
					
						
							|  |  |  | 	mediaID, _, err := parseFileName(form.FileName) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := gtserror.Newf("media file name %s not valid", form.FileName) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// Get the account that owns the media | 
					
						
							|  |  |  | 	// and make sure it's not suspended. | 
					
						
							|  |  |  | 	acctID := form.AccountID | 
					
						
							|  |  |  | 	acct, err := p.state.DB.GetAccountByID(ctx, acctID) | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		err := gtserror.Newf("db error getting account %s: %w", acctID, err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if acct.IsSuspended() { | 
					
						
							|  |  |  | 		err := gtserror.Newf("account %s is suspended", acctID) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// If requester was authenticated, ensure media | 
					
						
							|  |  |  | 	// owner and requester don't block each other. | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	if requester != nil { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		blocked, err := p.state.DB.IsEitherBlocked(ctx, requester.ID, acctID) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 			err := gtserror.Newf("db error checking block between %s and %s: %w", acctID, requester.ID, err) | 
					
						
							|  |  |  | 			return nil, gtserror.NewErrorNotFound(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 		if blocked { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 			err := gtserror.Newf("block exists between %s and %s", acctID, requester.ID) | 
					
						
							|  |  |  | 			return nil, gtserror.NewErrorNotFound(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// The way we store emojis is a bit different | 
					
						
							|  |  |  | 	// from the way we store other attachments, | 
					
						
							|  |  |  | 	// so we need to take different steps depending | 
					
						
							|  |  |  | 	// on the media type being requested. | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	switch mediaType { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-20 15:19:53 +01:00
										 |  |  | 	case media.TypeEmoji: | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		return p.getEmojiContent(ctx, | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 			acctID, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 			mediaSize, | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 			mediaID, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-20 15:19:53 +01:00
										 |  |  | 	case media.TypeAttachment, media.TypeHeader, media.TypeAvatar: | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		return p.getAttachmentContent(ctx, | 
					
						
							|  |  |  | 			requester, | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 			acctID, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 			mediaSize, | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 			mediaID, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		err := gtserror.Newf("media type %s not recognized", mediaType) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(err) | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | func (p *Processor) getAttachmentContent( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	requester *gtsmodel.Account, | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	acctID string, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	sizeStr media.Size, | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	mediaID string, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | ) ( | 
					
						
							|  |  |  | 	*apimodel.Content, | 
					
						
							|  |  |  | 	gtserror.WithCode, | 
					
						
							|  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// Get attachment with given ID from the database. | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	attach, err := p.state.DB.GetAttachmentByID(ctx, mediaID) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		err := gtserror.Newf("db error getting attachment %s: %w", mediaID, err) | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	if attach == nil { | 
					
						
							|  |  |  | 		const text = "media not found" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// Ensure the account | 
					
						
							|  |  |  | 	// actually owns the media. | 
					
						
							|  |  |  | 	if attach.AccountID != acctID { | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		const text = "media was not owned by passed account id" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(errors.New(text) /* no help text! */) | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// Unknown file types indicate no *locally* | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// stored data we can serve. Handle separately. | 
					
						
							|  |  |  | 	if attach.Type == gtsmodel.FileTypeUnknown { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		return handleUnknown(attach) | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	// If requester was provided, use their username | 
					
						
							|  |  |  | 	// to create a transport to potentially re-fetch | 
					
						
							|  |  |  | 	// the media. Else falls back to instance account. | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	var requestUser string | 
					
						
							|  |  |  | 	if requester != nil { | 
					
						
							|  |  |  | 		requestUser = requester.Username | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Ensure that stored media is cached. | 
					
						
							|  |  |  | 	// (this handles local media / recaches). | 
					
						
							|  |  |  | 	attach, err = p.federator.RefreshMedia( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		requestUser, | 
					
						
							|  |  |  | 		attach, | 
					
						
							|  |  |  | 		media.AdditionalMediaInfo{}, | 
					
						
							|  |  |  | 		false, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := gtserror.Newf("error recaching media: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Start preparing API content model. | 
					
						
							| 
									
										
										
										
											2024-12-05 13:35:07 +00:00
										 |  |  | 	apiContent := &apimodel.Content{} | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Retrieve appropriate | 
					
						
							|  |  |  | 	// size file from storage. | 
					
						
							|  |  |  | 	switch sizeStr { | 
					
						
							| 
									
										
										
										
											2023-01-16 16:19:17 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	case media.SizeOriginal: | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		apiContent.ContentType = attach.File.ContentType | 
					
						
							|  |  |  | 		apiContent.ContentLength = int64(attach.File.FileSize) | 
					
						
							|  |  |  | 		return p.getContent(ctx, | 
					
						
							|  |  |  | 			attach.File.Path, | 
					
						
							|  |  |  | 			apiContent, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	case media.SizeSmall: | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		apiContent.ContentType = attach.Thumbnail.ContentType | 
					
						
							|  |  |  | 		apiContent.ContentLength = int64(attach.Thumbnail.FileSize) | 
					
						
							|  |  |  | 		return p.getContent(ctx, | 
					
						
							|  |  |  | 			attach.Thumbnail.Path, | 
					
						
							|  |  |  | 			apiContent, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		const text = "invalid media attachment size" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorBadRequest(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2023-01-11 11:13:13 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | func (p *Processor) getEmojiContent( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	acctID string, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	sizeStr media.Size, | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 	emojiID string, | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | ) ( | 
					
						
							|  |  |  | 	*apimodel.Content, | 
					
						
							|  |  |  | 	gtserror.WithCode, | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 	// Reconstruct static emoji image URL to search for it. | 
					
						
							|  |  |  | 	// As refreshed emojis use a newly generated path ID to | 
					
						
							|  |  |  | 	// differentiate them (cache-wise) from the original. | 
					
						
							|  |  |  | 	staticURL := uris.URIForAttachment( | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		acctID, | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 		string(media.TypeEmoji), | 
					
						
							|  |  |  | 		string(media.SizeStatic), | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		emojiID, | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 		"png", | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2022-10-13 15:16:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Search for emoji with given static URL in the database. | 
					
						
							|  |  |  | 	emoji, err := p.state.DB.GetEmojiByStaticURL(ctx, staticURL) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		err := gtserror.Newf("error fetching emoji from database: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	if emoji == nil { | 
					
						
							|  |  |  | 		const text = "emoji not found" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	if *emoji.Disabled { | 
					
						
							|  |  |  | 		const text = "emoji has been disabled" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(errors.New(text), text) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Ensure that stored emoji is cached. | 
					
						
							|  |  |  | 	// (this handles local emoji / recaches). | 
					
						
							| 
									
										
										
										
											2024-08-03 17:05:38 +00:00
										 |  |  | 	emoji, err = p.federator.RecacheEmoji( | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		ctx, | 
					
						
							|  |  |  | 		emoji, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := gtserror.Newf("error recaching emoji: %w", err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorNotFound(err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Start preparing API content model. | 
					
						
							|  |  |  | 	apiContent := &apimodel.Content{} | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Retrieve appropriate | 
					
						
							|  |  |  | 	// size file from storage. | 
					
						
							|  |  |  | 	switch sizeStr { | 
					
						
							| 
									
										
										
										
											2023-07-24 13:14:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	case media.SizeOriginal: | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		apiContent.ContentType = emoji.ImageContentType | 
					
						
							|  |  |  | 		apiContent.ContentLength = int64(emoji.ImageFileSize) | 
					
						
							|  |  |  | 		return p.getContent(ctx, | 
					
						
							|  |  |  | 			emoji.ImagePath, | 
					
						
							|  |  |  | 			apiContent, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	case media.SizeStatic: | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		apiContent.ContentType = emoji.ImageStaticContentType | 
					
						
							|  |  |  | 		apiContent.ContentLength = int64(emoji.ImageStaticFileSize) | 
					
						
							|  |  |  | 		return p.getContent(ctx, | 
					
						
							|  |  |  | 			emoji.ImageStaticPath, | 
					
						
							|  |  |  | 			apiContent, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		const text = "invalid media attachment size" | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorBadRequest(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2022-03-07 11:08:26 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | // getContent performs the final file fetching of | 
					
						
							|  |  |  | // stored content at path in storage. This is | 
					
						
							|  |  |  | // populated in the apimodel.Content{} and returned. | 
					
						
							|  |  |  | // (note: this also handles un-proxied S3 storage). | 
					
						
							|  |  |  | func (p *Processor) getContent( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	path string, | 
					
						
							|  |  |  | 	content *apimodel.Content, | 
					
						
							|  |  |  | ) ( | 
					
						
							|  |  |  | 	*apimodel.Content, | 
					
						
							|  |  |  | 	gtserror.WithCode, | 
					
						
							|  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2023-02-12 14:42:28 +01:00
										 |  |  | 	// If running on S3 storage with proxying disabled then | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// just fetch pre-signed URL instead of the content. | 
					
						
							|  |  |  | 	if url := p.state.Storage.URL(ctx, path); url != nil { | 
					
						
							| 
									
										
										
										
											2022-07-03 12:08:30 +02:00
										 |  |  | 		content.URL = url | 
					
						
							|  |  |  | 		return content, nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-02-12 14:42:28 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Fetch file stream for the stored media at path. | 
					
						
							|  |  |  | 	rc, err := p.state.Storage.GetStream(ctx, path) | 
					
						
							|  |  |  | 	if err != nil && !storage.IsNotFound(err) { | 
					
						
							|  |  |  | 		err := gtserror.Newf("error getting file %s from storage: %w", path, err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	// Ensure found. | 
					
						
							|  |  |  | 	if rc == nil { | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		err := gtserror.Newf("file not found at %s", path) | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 		const text = "file not found" | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 		return nil, gtserror.NewErrorNotFound(err, text) | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Return with stream. | 
					
						
							|  |  |  | 	content.Content = rc | 
					
						
							| 
									
										
										
										
											2021-07-05 13:23:03 +02:00
										 |  |  | 	return content, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | // handles serving Content for "unknown" file | 
					
						
							|  |  |  | // type, ie., a file we couldn't cache (this time). | 
					
						
							|  |  |  | func handleUnknown( | 
					
						
							|  |  |  | 	attach *gtsmodel.MediaAttachment, | 
					
						
							|  |  |  | ) (*apimodel.Content, gtserror.WithCode) { | 
					
						
							|  |  |  | 	if attach.RemoteURL == "" { | 
					
						
							|  |  |  | 		err := gtserror.Newf("empty remote url for %s", attach.ID) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Parse media remote URL to valid URL object. | 
					
						
							|  |  |  | 	remoteURL, err := url.Parse(attach.RemoteURL) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := gtserror.Newf("invalid remote url for %s: %w", attach.ID, err) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if remoteURL == nil { | 
					
						
							|  |  |  | 		err := gtserror.Newf("nil remote url for %s", attach.ID) | 
					
						
							|  |  |  | 		return nil, gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Just forward the request to the remote URL, | 
					
						
							|  |  |  | 	// since this is a type we couldn't process. | 
					
						
							|  |  |  | 	url := &storage.PresignedURL{ | 
					
						
							|  |  |  | 		URL: remoteURL, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// We might manage to cache the media | 
					
						
							|  |  |  | 		// at some point, so set a low-ish expiry. | 
					
						
							|  |  |  | 		Expiry: time.Now().Add(2 * time.Hour), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &apimodel.Content{URL: url}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-26 15:01:16 +00:00
										 |  |  | func parseType(s string) (media.Type, error) { | 
					
						
							|  |  |  | 	switch s { | 
					
						
							|  |  |  | 	case string(media.TypeAttachment): | 
					
						
							|  |  |  | 		return media.TypeAttachment, nil | 
					
						
							|  |  |  | 	case string(media.TypeHeader): | 
					
						
							|  |  |  | 		return media.TypeHeader, nil | 
					
						
							|  |  |  | 	case string(media.TypeAvatar): | 
					
						
							|  |  |  | 		return media.TypeAvatar, nil | 
					
						
							|  |  |  | 	case string(media.TypeEmoji): | 
					
						
							|  |  |  | 		return media.TypeEmoji, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return "", fmt.Errorf("%s not a recognized media.Type", s) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func parseSize(s string) (media.Size, error) { | 
					
						
							|  |  |  | 	switch s { | 
					
						
							|  |  |  | 	case string(media.SizeSmall): | 
					
						
							|  |  |  | 		return media.SizeSmall, nil | 
					
						
							|  |  |  | 	case string(media.SizeOriginal): | 
					
						
							|  |  |  | 		return media.SizeOriginal, nil | 
					
						
							|  |  |  | 	case string(media.SizeStatic): | 
					
						
							|  |  |  | 		return media.SizeStatic, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return "", fmt.Errorf("%s not a recognized media.Size", s) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-07-22 18:45:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Extract the mediaID and file extension from | 
					
						
							|  |  |  | // a string like "01J3CTH8CZ6ATDNMG6CPRC36XE.gif" | 
					
						
							|  |  |  | func parseFileName(s string) (string, string, error) { | 
					
						
							|  |  |  | 	spl := strings.Split(s, ".") | 
					
						
							|  |  |  | 	if len(spl) != 2 || spl[0] == "" || spl[1] == "" { | 
					
						
							|  |  |  | 		return "", "", errors.New("file name not splittable on '.'") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		mediaID  = spl[0] | 
					
						
							|  |  |  | 		mediaExt = spl[1] | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !regexes.ULID.MatchString(mediaID) { | 
					
						
							|  |  |  | 		return "", "", fmt.Errorf("%s not a valid ULID", mediaID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return mediaID, mediaExt, nil | 
					
						
							|  |  |  | } |