mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 05:52:25 -05:00 
			
		
		
		
	rewrite file serving system
This commit is contained in:
		
					parent
					
						
							
								e47ee2b883
							
						
					
				
			
			
				commit
				
					
						cc424df169
					
				
			
		
					 12 changed files with 355 additions and 226 deletions
				
			
		|  | @ -21,12 +21,11 @@ package fileserver | |||
| import ( | ||||
| 	"bytes" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| ) | ||||
| 
 | ||||
| // ServeFile is for serving attachments, headers, and avatars to the requester from instance storage. | ||||
|  | @ -42,6 +41,12 @@ func (m *FileServer) ServeFile(c *gin.Context) { | |||
| 	}) | ||||
| 	l.Trace("received request") | ||||
| 
 | ||||
| 	authed, err := oauth.Authed(c, false, false, false, false) | ||||
| 	if err != nil { | ||||
| 		c.String(http.StatusNotFound, "404 page not found") | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// We use request params to check what to pull out of the database/storage so check everything. A request URL should be formatted as follows: | ||||
| 	// "https://example.org/fileserver/[ACCOUNT_ID]/[MEDIA_TYPE]/[MEDIA_SIZE]/[FILE_NAME]" | ||||
| 	// "FILE_NAME" consists of two parts, the attachment's database id, a period, and the file extension. | ||||
|  | @ -73,171 +78,16 @@ func (m *FileServer) ServeFile(c *gin.Context) { | |||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Only serve media types that are defined in our internal media module | ||||
| 	switch mediaType { | ||||
| 	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(), | ||||
| 	content, err := m.processor.MediaGet(authed, &model.GetContentRequestForm{ | ||||
| 		AccountID: accountID, | ||||
| 		MediaType: mediaType, | ||||
| 		MediaSize: mediaSize, | ||||
| 		FileName:  fileName, | ||||
| 	}) | ||||
| 
 | ||||
| 	// This corresponds to original-sized image as it was uploaded, small (which is the thumbnail), or static | ||||
| 	switch mediaSize { | ||||
| 	case media.MediaOriginal, media.MediaSmall, 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 | ||||
| 	} | ||||
| 	wantedMediaID := spl[0] | ||||
| 	fileExtension := spl[1] | ||||
| 	if wantedMediaID == "" || 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 | ||||
| 	attachment := >smodel.MediaAttachment{} | ||||
| 	if err := m.db.GetByID(wantedMediaID, attachment); err != nil { | ||||
| 		l.Debugf("attachment with id %s not retrievable: %s", wantedMediaID, err) | ||||
| 		c.String(http.StatusNotFound, "404 page not found") | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// make sure the given account id owns the requested attachment | ||||
| 	if accountID != attachment.AccountID { | ||||
| 		l.Debugf("account %s does not own attachment with id %s", accountID, wantedMediaID) | ||||
| 		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 = attachment.File.Path | ||||
| 		contentType = attachment.File.ContentType | ||||
| 		contentLength = attachment.File.FileSize | ||||
| 	case media.MediaSmall: | ||||
| 		storagePath = attachment.Thumbnail.Path | ||||
| 		contentType = attachment.Thumbnail.ContentType | ||||
| 		contentLength = attachment.Thumbnail.FileSize | ||||
| 	} | ||||
| 
 | ||||
| 	// use the path listed on the attachment we pulled out of the database to retrieve the object from storage | ||||
| 	attachmentBytes, err := m.storage.RetrieveFileFrom(storagePath) | ||||
| 	if err != nil { | ||||
| 		l.Debugf("error retrieving from storage: %s", err) | ||||
| 		c.String(http.StatusNotFound, "404 page not found") | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	l.Errorf("about to serve content length: %d attachment bytes is: %d", int64(contentLength), int64(len(attachmentBytes))) | ||||
| 
 | ||||
| 	// 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.GetLocalAccountByUsername(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{}) | ||||
| 	c.DataFromReader(http.StatusOK, content.ContentLength, content.ContentType, bytes.NewReader(content.Content), nil) | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue