web.go (5407B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package web 19 20 import ( 21 "context" 22 "net/http" 23 "net/url" 24 "path/filepath" 25 26 "codeberg.org/gruf/go-cache/v3" 27 "github.com/gin-gonic/gin" 28 "github.com/superseriousbusiness/gotosocial/internal/config" 29 "github.com/superseriousbusiness/gotosocial/internal/db" 30 "github.com/superseriousbusiness/gotosocial/internal/log" 31 "github.com/superseriousbusiness/gotosocial/internal/middleware" 32 "github.com/superseriousbusiness/gotosocial/internal/processing" 33 "github.com/superseriousbusiness/gotosocial/internal/router" 34 "github.com/superseriousbusiness/gotosocial/internal/uris" 35 ) 36 37 const ( 38 confirmEmailPath = "/" + uris.ConfirmEmailPath 39 profileGroupPath = "/@:" + usernameKey 40 statusPath = "/statuses/:" + statusIDKey // leave out the '/@:username' prefix as this will be served within the profile group 41 customCSSPath = profileGroupPath + "/custom.css" 42 rssFeedPath = profileGroupPath + "/feed.rss" 43 assetsPathPrefix = "/assets" 44 distPathPrefix = assetsPathPrefix + "/dist" 45 settingsPathPrefix = "/settings" 46 settingsPanelGlob = settingsPathPrefix + "/*panel" 47 userPanelPath = settingsPathPrefix + "/user" 48 adminPanelPath = settingsPathPrefix + "/admin" 49 50 tokenParam = "token" 51 usernameKey = "username" 52 statusIDKey = "status" 53 54 cacheControlHeader = "Cache-Control" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control 55 cacheControlNoCache = "no-cache" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives 56 ifModifiedSinceHeader = "If-Modified-Since" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since 57 ifNoneMatchHeader = "If-None-Match" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match 58 eTagHeader = "ETag" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag 59 lastModifiedHeader = "Last-Modified" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified 60 ) 61 62 type Module struct { 63 processor *processing.Processor 64 eTagCache cache.Cache[string, eTagCacheEntry] 65 isURIBlocked func(context.Context, *url.URL) (bool, db.Error) 66 } 67 68 func New(db db.DB, processor *processing.Processor) *Module { 69 return &Module{ 70 processor: processor, 71 eTagCache: newETagCache(), 72 isURIBlocked: db.IsURIBlocked, 73 } 74 } 75 76 func (m *Module) Route(r router.Router, mi ...gin.HandlerFunc) { 77 // Group all static files from assets dir at /assets, 78 // so that they can use the same cache control middleware. 79 webAssetsAbsFilePath, err := filepath.Abs(config.GetWebAssetBaseDir()) 80 if err != nil { 81 log.Panicf(nil, "error getting absolute path of assets dir: %s", err) 82 } 83 fs := fileSystem{http.Dir(webAssetsAbsFilePath)} 84 assetsGroup := r.AttachGroup(assetsPathPrefix) 85 assetsGroup.Use(m.assetsCacheControlMiddleware(fs)) 86 assetsGroup.Use(mi...) 87 assetsGroup.StaticFS("/", fs) 88 89 // handlers that serve profiles and statuses should use the SignatureCheck 90 // middleware, so that requests with content-type application/activity+json 91 // can still be served 92 profileGroup := r.AttachGroup(profileGroupPath) 93 profileGroup.Use(mi...) 94 profileGroup.Use(middleware.SignatureCheck(m.isURIBlocked), middleware.CacheControl("no-store")) 95 profileGroup.Handle(http.MethodGet, "", m.profileGETHandler) // use empty path here since it's the base of the group 96 profileGroup.Handle(http.MethodGet, statusPath, m.threadGETHandler) 97 98 // Attach individual web handlers which require no specific middlewares 99 r.AttachHandler(http.MethodGet, "/", m.baseHandler) // front-page 100 r.AttachHandler(http.MethodGet, settingsPathPrefix, m.SettingsPanelHandler) 101 r.AttachHandler(http.MethodGet, settingsPanelGlob, m.SettingsPanelHandler) 102 r.AttachHandler(http.MethodGet, customCSSPath, m.customCSSGETHandler) 103 r.AttachHandler(http.MethodGet, rssFeedPath, m.rssFeedGETHandler) 104 r.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler) 105 r.AttachHandler(http.MethodGet, robotsPath, m.robotsGETHandler) 106 r.AttachHandler(http.MethodGet, aboutPath, m.aboutGETHandler) 107 r.AttachHandler(http.MethodGet, domainBlockListPath, m.domainBlockListGETHandler) 108 109 // Attach redirects from old endpoints to current ones for backwards compatibility 110 r.AttachHandler(http.MethodGet, "/auth/edit", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, userPanelPath) }) 111 r.AttachHandler(http.MethodGet, "/user", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, userPanelPath) }) 112 r.AttachHandler(http.MethodGet, "/admin", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, adminPanelPath) }) 113 }