GoBlog/http.go

644 lines
19 KiB
Go
Raw Normal View History

2020-07-28 19:17:07 +00:00
package main
import (
2020-10-19 19:09:51 +00:00
"compress/flate"
2020-12-13 14:16:47 +00:00
"fmt"
2021-03-10 17:08:20 +00:00
"log"
"net"
2020-07-28 19:17:07 +00:00
"net/http"
2021-02-16 20:27:52 +00:00
"net/url"
2020-07-28 19:17:07 +00:00
"strconv"
2021-02-16 20:27:52 +00:00
"strings"
2021-03-19 10:26:45 +00:00
"sync"
2021-03-31 07:29:52 +00:00
"time"
2020-09-25 17:23:01 +00:00
"github.com/caddyserver/certmagic"
2021-01-23 16:24:47 +00:00
"github.com/dchest/captcha"
2021-03-03 17:19:55 +00:00
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
2021-03-10 17:47:56 +00:00
servertiming "github.com/mitchellh/go-server-timing"
"golang.org/x/net/context"
2020-07-28 19:17:07 +00:00
)
const (
contentType = "Content-Type"
2020-07-31 14:23:29 +00:00
charsetUtf8Suffix = "; charset=utf-8"
contentTypeHTML = "text/html"
2021-05-08 19:22:48 +00:00
contentTypeXML = "text/xml"
contentTypeJSON = "application/json"
contentTypeWWWForm = "application/x-www-form-urlencoded"
contentTypeMultipartForm = "multipart/form-data"
contentTypeAS = "application/activity+json"
contentTypeRSS = "application/rss+xml"
contentTypeATOM = "application/atom+xml"
contentTypeJSONFeed = "application/feed+json"
contentTypeHTMLUTF8 = contentTypeHTML + charsetUtf8Suffix
2021-05-08 19:22:48 +00:00
contentTypeXMLUTF8 = contentTypeXML + charsetUtf8Suffix
contentTypeJSONUTF8 = contentTypeJSON + charsetUtf8Suffix
contentTypeASUTF8 = contentTypeAS + charsetUtf8Suffix
userAgent = "User-Agent"
appUserAgent = "GoBlog"
)
func (a *goBlog) startServer() (err error) {
log.Println("Start server(s)...")
2020-11-03 17:58:32 +00:00
// Start
a.d = &dynamicHandler{}
2021-03-10 17:47:56 +00:00
// Set basic middlewares
var finalHandler http.Handler = a.d
if a.cfg.Server.PublicHTTPS || a.cfg.Server.SecurityHeaders {
finalHandler = a.securityHeaders(finalHandler)
2021-03-10 17:47:56 +00:00
}
finalHandler = servertiming.Middleware(finalHandler, nil)
finalHandler = middleware.Heartbeat("/ping")(finalHandler)
2021-03-10 17:47:56 +00:00
finalHandler = middleware.Compress(flate.DefaultCompression)(finalHandler)
finalHandler = middleware.Recoverer(finalHandler)
if a.cfg.Server.Logging {
finalHandler = a.logMiddleware(finalHandler)
2021-03-10 17:47:56 +00:00
}
2021-03-19 12:04:11 +00:00
// Create routers that don't change
if err = a.buildStaticHandlersRouters(); err != nil {
return err
2021-03-19 12:04:11 +00:00
}
2021-03-10 17:47:56 +00:00
// Load router
if err = a.reloadRouter(); err != nil {
return err
2020-07-28 19:17:07 +00:00
}
2021-03-19 09:10:47 +00:00
// Start Onion service
if a.cfg.Server.Tor {
2021-03-19 09:10:47 +00:00
go func() {
if err := a.startOnionService(finalHandler); err != nil {
log.Println("Tor failed:", err.Error())
}
2021-03-19 09:10:47 +00:00
}()
}
// Start server
s := &http.Server{
Handler: finalHandler,
ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
}
a.shutdown.Add(shutdownServer(s, "main server"))
if a.cfg.Server.PublicHTTPS {
// Configure
certmagic.Default.Storage = &certmagic.FileStorage{Path: "data/https"}
certmagic.DefaultACME.Email = a.cfg.Server.LetsEncryptMail
certmagic.DefaultACME.CA = certmagic.LetsEncryptProductionCA
// Start HTTP server for redirects
httpServer := &http.Server{
Addr: ":http",
Handler: http.HandlerFunc(redirectToHttps),
ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
}
a.shutdown.Add(shutdownServer(httpServer, "http server"))
go func() {
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Println("Failed to start HTTP server:", err.Error())
}
}()
// Start HTTPS
s.Addr = ":https"
hosts := []string{a.cfg.Server.publicHostname}
if a.cfg.Server.shortPublicHostname != "" {
hosts = append(hosts, a.cfg.Server.shortPublicHostname)
2020-12-24 10:00:16 +00:00
}
listener, e := certmagic.Listen(hosts)
if e != nil {
return e
2021-03-31 07:29:52 +00:00
}
if err = s.Serve(listener); err != nil && err != http.ErrServerClosed {
return err
}
} else {
s.Addr = ":" + strconv.Itoa(a.cfg.Server.Port)
if err = s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return err
}
2020-07-29 14:41:36 +00:00
}
return nil
2020-08-01 15:49:46 +00:00
}
2020-07-29 14:41:36 +00:00
2021-05-07 14:14:15 +00:00
func shutdownServer(s *http.Server, name string) func() {
return func() {
toc, c := context.WithTimeout(context.Background(), 5*time.Second)
defer c()
if err := s.Shutdown(toc); err != nil {
log.Printf("Error on server shutdown (%v): %v", name, err)
}
2021-05-07 14:14:15 +00:00
log.Println("Stopped server:", name)
}
}
func redirectToHttps(w http.ResponseWriter, r *http.Request) {
requestHost, _, err := net.SplitHostPort(r.Host)
if err != nil {
requestHost = r.Host
}
w.Header().Set("Connection", "close")
http.Redirect(w, r, fmt.Sprintf("https://%s%s", requestHost, r.URL.RequestURI()), http.StatusMovedPermanently)
}
func (a *goBlog) reloadRouter() error {
h, err := a.buildDynamicRouter()
2020-07-31 19:44:16 +00:00
if err != nil {
return err
}
a.d.swapHandler(h)
a.cache.purge()
2020-07-31 19:44:16 +00:00
return nil
}
2021-03-19 12:04:11 +00:00
const (
paginationPath = "/page/{page:[0-9-]+}"
feedPath = ".{feed:rss|json|atom}"
)
2021-01-30 18:37:26 +00:00
func (a *goBlog) buildStaticHandlersRouters() error {
if pm := a.cfg.PrivateMode; pm != nil && pm.Enabled {
a.privateMode = true
a.privateModeHandler = append(a.privateModeHandler, a.authMiddleware)
} else {
a.privateMode = false
a.privateModeHandler = []func(http.Handler) http.Handler{}
2021-02-27 07:31:06 +00:00
}
a.captchaHandler = captcha.Server(500, 250)
a.micropubRouter = chi.NewRouter()
a.micropubRouter.Use(a.checkIndieAuth)
a.micropubRouter.Get("/", a.serveMicropubQuery)
a.micropubRouter.Post("/", a.serveMicropubPost)
a.micropubRouter.Post(micropubMediaSubPath, a.serveMicropubMedia)
a.indieAuthRouter = chi.NewRouter()
a.indieAuthRouter.Get("/", a.indieAuthRequest)
a.indieAuthRouter.With(a.authMiddleware).Post("/accept", a.indieAuthAccept)
a.indieAuthRouter.Post("/", a.indieAuthVerification)
a.indieAuthRouter.Get("/token", a.indieAuthToken)
a.indieAuthRouter.Post("/token", a.indieAuthToken)
a.webmentionsRouter = chi.NewRouter()
if wm := a.cfg.Webmention; wm != nil && !wm.DisableReceiving {
a.webmentionsRouter.Post("/", a.handleWebmention)
a.webmentionsRouter.Group(func(r chi.Router) {
// Authenticated routes
r.Use(a.authMiddleware)
r.Get("/", a.webmentionAdmin)
r.Get(paginationPath, a.webmentionAdmin)
r.Post("/delete", a.webmentionAdminDelete)
r.Post("/approve", a.webmentionAdminApprove)
r.Post("/reverify", a.webmentionAdminReverify)
})
}
2021-03-19 12:04:11 +00:00
a.notificationsRouter = chi.NewRouter()
a.notificationsRouter.Use(a.authMiddleware)
a.notificationsRouter.Get("/", a.notificationsAdmin)
a.notificationsRouter.Get(paginationPath, a.notificationsAdmin)
a.notificationsRouter.Post("/delete", a.notificationsAdminDelete)
2021-03-19 12:04:11 +00:00
if ap := a.cfg.ActivityPub; ap != nil && ap.Enabled {
a.activitypubRouter = chi.NewRouter()
a.activitypubRouter.Post("/inbox/{blog}", a.apHandleInbox)
a.activitypubRouter.Post("/{blog}/inbox", a.apHandleInbox)
2021-03-19 12:04:11 +00:00
}
a.editorRouter = chi.NewRouter()
a.editorRouter.Use(a.authMiddleware)
a.editorRouter.Get("/", a.serveEditor)
a.editorRouter.Post("/", a.serveEditorPost)
a.commentsRouter = chi.NewRouter()
a.commentsRouter.Use(a.privateModeHandler...)
a.commentsRouter.With(a.cache.cacheMiddleware, noIndexHeader).Get("/{id:[0-9]+}", a.serveComment)
a.commentsRouter.With(a.captchaMiddleware).Post("/", a.createComment)
a.commentsRouter.Group(func(r chi.Router) {
// Admin
r.Use(a.authMiddleware)
r.Get("/", a.commentsAdmin)
r.Get(paginationPath, a.commentsAdmin)
r.Post("/delete", a.commentsAdminDelete)
})
2021-03-19 12:04:11 +00:00
a.searchRouter = chi.NewRouter()
a.searchRouter.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(a.cache.cacheMiddleware)
r.Get("/", a.serveSearch)
r.Post("/", a.serveSearch)
searchResultPath := "/" + searchPlaceholder
r.Get(searchResultPath, a.serveSearchResult)
r.Get(searchResultPath+feedPath, a.serveSearchResult)
r.Get(searchResultPath+paginationPath, a.serveSearchResult)
})
a.searchRouter.With(a.cache.cacheMiddleware).Get("/opensearch.xml", a.serveOpenSearch)
a.setBlogMiddlewares = map[string]func(http.Handler) http.Handler{}
a.sectionMiddlewares = map[string]func(http.Handler) http.Handler{}
a.taxonomyMiddlewares = map[string]func(http.Handler) http.Handler{}
a.photosMiddlewares = map[string]func(http.Handler) http.Handler{}
a.searchMiddlewares = map[string]func(http.Handler) http.Handler{}
a.customPagesMiddlewares = map[string]func(http.Handler) http.Handler{}
a.commentsMiddlewares = map[string]func(http.Handler) http.Handler{}
for blog, blogConfig := range a.cfg.Blogs {
sbm := middleware.WithValue(blogContextKey, blog)
a.setBlogMiddlewares[blog] = sbm
for _, section := range blogConfig.Sections {
if section.Name != "" {
secPath := blogConfig.getRelativePath(section.Name)
a.sectionMiddlewares[secPath] = middleware.WithValue(indexConfigKey, &indexConfig{
path: secPath,
section: section,
})
}
}
for _, taxonomy := range blogConfig.Taxonomies {
if taxonomy.Name != "" {
taxPath := blogConfig.getRelativePath(taxonomy.Name)
a.taxonomyMiddlewares[taxPath] = middleware.WithValue(taxonomyContextKey, taxonomy)
}
}
if blogConfig.Photos != nil && blogConfig.Photos.Enabled {
a.photosMiddlewares[blog] = middleware.WithValue(indexConfigKey, &indexConfig{
path: blogConfig.getRelativePath(blogConfig.Photos.Path),
parameter: blogConfig.Photos.Parameter,
title: blogConfig.Photos.Title,
description: blogConfig.Photos.Description,
summaryTemplate: templatePhotosSummary,
})
}
if blogConfig.Search != nil && blogConfig.Search.Enabled {
a.searchMiddlewares[blog] = middleware.WithValue(pathContextKey, blogConfig.getRelativePath(blogConfig.Search.Path))
}
for _, cp := range blogConfig.CustomPages {
a.customPagesMiddlewares[cp.Path] = middleware.WithValue(customPageContextKey, cp)
}
if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled {
a.commentsMiddlewares[blog] = middleware.WithValue(pathContextKey, blogConfig.getRelativePath("/comment"))
}
2021-03-19 12:04:11 +00:00
}
return nil
}
var (
taxValueMiddlewares = map[string]func(http.Handler) http.Handler{}
)
func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
2021-03-19 12:04:11 +00:00
r := chi.NewRouter()
2021-02-27 07:31:06 +00:00
// Basic middleware
r.Use(a.redirectShortDomain)
2020-11-05 16:06:42 +00:00
r.Use(middleware.RedirectSlashes)
2020-12-22 19:29:25 +00:00
r.Use(middleware.CleanPath)
2020-10-19 19:09:51 +00:00
r.Use(middleware.GetHead)
if !a.cfg.Cache.Enable {
r.Use(middleware.NoCache)
}
2021-02-27 07:31:06 +00:00
// No Index Header
if a.privateMode {
2021-02-27 07:31:06 +00:00
r.Use(noIndexHeader)
}
// Login middleware etc.
r.Use(a.checkIsLogin)
r.Use(a.checkIsCaptcha)
r.Use(a.checkLoggedIn)
// Logout
r.With(a.authMiddleware).Get("/login", serveLogin)
r.With(a.authMiddleware).Get("/logout", a.serveLogout)
2020-10-06 17:07:48 +00:00
// Micropub
r.Mount(micropubPath, a.micropubRouter)
2020-10-06 17:07:48 +00:00
2020-10-13 19:35:39 +00:00
// IndieAuth
r.Mount("/indieauth", a.indieAuthRouter)
2020-10-13 19:35:39 +00:00
// ActivityPub and stuff
if ap := a.cfg.ActivityPub; ap != nil && ap.Enabled {
r.Mount("/activitypub", a.activitypubRouter)
r.With(a.cache.cacheMiddleware).Get("/.well-known/webfinger", a.apHandleWebfinger)
r.With(a.cache.cacheMiddleware).Get("/.well-known/host-meta", handleWellKnownHostMeta)
r.With(a.cache.cacheMiddleware).Get("/.well-known/nodeinfo", a.serveNodeInfoDiscover)
r.With(a.cache.cacheMiddleware).Get("/nodeinfo", a.serveNodeInfo)
}
// Webmentions
r.Mount(webmentionPath, a.webmentionsRouter)
// Notifications
r.Mount(notificationsPath, a.notificationsRouter)
// Posts
pp, err := a.db.allPostPaths(statusPublished)
if err != nil {
return nil, err
2020-07-30 19:18:13 +00:00
}
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(a.checkActivityStreamsRequest, a.cache.cacheMiddleware)
for _, path := range pp {
r.Get(path, a.servePost)
}
})
2021-01-15 20:56:46 +00:00
// Drafts
dp, err := a.db.allPostPaths(statusDraft)
2021-01-15 20:56:46 +00:00
if err != nil {
return nil, err
}
r.Group(func(r chi.Router) {
r.Use(a.authMiddleware)
for _, path := range dp {
r.Get(path, a.servePost)
2021-01-15 20:56:46 +00:00
}
})
2021-01-15 20:56:46 +00:00
2020-11-09 17:33:56 +00:00
// Post aliases
allPostAliases, err := a.db.allPostAliases()
if err != nil {
return nil, err
2020-07-30 19:18:13 +00:00
}
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(a.cache.cacheMiddleware)
for _, path := range allPostAliases {
r.Get(path, a.servePostAlias)
}
})
// Assets
for _, path := range a.allAssetPaths() {
r.Get(path, a.serveAsset)
}
2020-12-23 13:11:14 +00:00
// Static files
for _, path := range allStaticPaths() {
r.Get(path, a.serveStaticFile)
2020-12-23 13:11:14 +00:00
}
2021-01-10 14:59:43 +00:00
// Media files
r.With(a.privateModeHandler...).Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, a.serveMediaFile)
2021-01-10 14:59:43 +00:00
2021-01-23 16:24:47 +00:00
// Captcha
r.Handle("/captcha/*", a.captchaHandler)
2021-01-23 16:24:47 +00:00
2020-12-22 21:15:29 +00:00
// Short paths
r.With(a.privateModeHandler...).With(a.cache.cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", a.redirectToLongPath)
2020-12-22 21:15:29 +00:00
for blog, blogConfig := range a.cfg.Blogs {
sbm := a.setBlogMiddlewares[blog]
2020-08-31 19:12:43 +00:00
// Sections
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
for _, section := range blogConfig.Sections {
if section.Name != "" {
secPath := blogConfig.getRelativePath(section.Name)
r.Group(func(r chi.Router) {
r.Use(a.sectionMiddlewares[secPath])
r.Get(secPath, a.serveIndex)
r.Get(secPath+feedPath, a.serveIndex)
r.Get(secPath+paginationPath, a.serveIndex)
})
}
2020-08-31 19:12:43 +00:00
}
})
2020-10-06 17:07:48 +00:00
// Taxonomies
2020-10-06 17:07:48 +00:00
for _, taxonomy := range blogConfig.Taxonomies {
if taxonomy.Name != "" {
taxPath := blogConfig.getRelativePath(taxonomy.Name)
taxValues, err := a.db.allTaxonomyValues(blog, taxonomy.Name)
2020-10-06 17:07:48 +00:00
if err != nil {
return nil, err
}
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
r.With(a.taxonomyMiddlewares[taxPath]).Get(taxPath, a.serveTaxonomy)
for _, tv := range taxValues {
r.Group(func(r chi.Router) {
vPath := taxPath + "/" + urlize(tv)
if _, ok := taxValueMiddlewares[vPath]; !ok {
taxValueMiddlewares[vPath] = middleware.WithValue(indexConfigKey, &indexConfig{
path: vPath,
tax: taxonomy,
taxValue: tv,
})
}
r.Use(taxValueMiddlewares[vPath])
r.Get(vPath, a.serveIndex)
r.Get(vPath+feedPath, a.serveIndex)
r.Get(vPath+paginationPath, a.serveIndex)
})
}
})
2020-08-31 19:12:43 +00:00
}
2020-08-25 18:55:32 +00:00
}
2020-10-06 17:07:48 +00:00
// Photos
2021-01-04 19:29:49 +00:00
if blogConfig.Photos != nil && blogConfig.Photos.Enabled {
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm, a.photosMiddlewares[blog])
photoPath := blogConfig.getRelativePath(blogConfig.Photos.Path)
r.Get(photoPath, a.serveIndex)
r.Get(photoPath+feedPath, a.serveIndex)
r.Get(photoPath+paginationPath, a.serveIndex)
})
2020-10-06 17:07:48 +00:00
}
2020-09-21 16:03:05 +00:00
2020-11-15 10:34:48 +00:00
// Search
2021-01-04 19:29:49 +00:00
if blogConfig.Search != nil && blogConfig.Search.Enabled {
searchPath := blogConfig.getRelativePath(blogConfig.Search.Path)
r.With(sbm, a.searchMiddlewares[blog]).Mount(searchPath, a.searchRouter)
2020-11-15 10:34:48 +00:00
}
2021-01-04 19:29:49 +00:00
// Stats
if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled {
statsPath := blogConfig.getRelativePath(blogConfig.BlogStats.Path)
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
r.Get(statsPath, a.serveBlogStats)
r.Get(statsPath+".table.html", a.serveBlogStatsTable)
})
2021-01-04 19:29:49 +00:00
}
// Date archives
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
yearRegex := `/{year:x|\d\d\d\d}`
monthRegex := `/{month:x|\d\d}`
dayRegex := `/{day:\d\d}`
yearPath := blogConfig.getRelativePath(yearRegex)
r.Get(yearPath, a.serveDate)
r.Get(yearPath+feedPath, a.serveDate)
r.Get(yearPath+paginationPath, a.serveDate)
monthPath := yearPath + monthRegex
r.Get(monthPath, a.serveDate)
r.Get(monthPath+feedPath, a.serveDate)
r.Get(monthPath+paginationPath, a.serveDate)
dayPath := monthPath + dayRegex
r.Get(dayPath, a.serveDate)
r.Get(dayPath+feedPath, a.serveDate)
r.Get(dayPath+paginationPath, a.serveDate)
})
2020-12-13 14:16:47 +00:00
2020-10-06 17:07:48 +00:00
// Blog
2020-12-23 15:53:10 +00:00
if !blogConfig.PostAsHome {
2021-02-27 07:31:06 +00:00
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(sbm)
2021-06-11 06:24:41 +00:00
blogBasePath := blogConfig.getRelativePath("")
r.With(a.checkActivityStreamsRequest, a.cache.cacheMiddleware).Get(blogBasePath, a.serveHome)
r.With(a.cache.cacheMiddleware).Get(blogBasePath+feedPath, a.serveHome)
r.With(a.cache.cacheMiddleware).Get(blogBasePath+paginationPath, a.serveHome)
2021-02-27 07:31:06 +00:00
})
}
2020-10-13 11:40:16 +00:00
// Custom pages
for _, cp := range blogConfig.CustomPages {
scp := a.customPagesMiddlewares[cp.Path]
2020-10-13 11:40:16 +00:00
if cp.Cache {
r.With(a.privateModeHandler...).With(a.cache.cacheMiddleware, sbm, scp).Get(cp.Path, a.serveCustomPage)
2020-10-13 11:40:16 +00:00
} else {
r.With(a.privateModeHandler...).With(sbm, scp).Get(cp.Path, a.serveCustomPage)
2020-10-13 11:40:16 +00:00
}
}
2021-01-17 11:53:07 +00:00
// Random post
if rp := blogConfig.RandomPost; rp != nil && rp.Enabled {
randomPath := rp.Path
if randomPath == "" {
randomPath = "/random"
}
r.With(a.privateModeHandler...).With(sbm).Get(blogConfig.getRelativePath(randomPath), a.redirectToRandomPost)
}
2021-01-17 11:53:07 +00:00
// Editor
r.With(sbm).Mount(blogConfig.getRelativePath("/editor"), a.editorRouter)
2021-01-23 16:24:47 +00:00
// Comments
if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled {
r.With(sbm, a.commentsMiddlewares[blog]).Mount(blogConfig.getRelativePath("/comment"), a.commentsRouter)
2021-01-23 16:24:47 +00:00
}
2021-05-08 19:22:48 +00:00
// Blogroll
if brConfig := blogConfig.Blogroll; brConfig != nil && brConfig.Enabled {
brPath := blogConfig.getRelativePath(brConfig.Path)
2021-05-08 19:22:48 +00:00
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(a.cache.cacheMiddleware, sbm)
r.Get(brPath, a.serveBlogroll)
r.Get(brPath+".opml", a.serveBlogrollExport)
2021-05-08 19:22:48 +00:00
})
}
2020-08-05 17:14:10 +00:00
}
2020-09-22 15:08:34 +00:00
// Sitemap
r.With(a.privateModeHandler...).With(a.cache.cacheMiddleware).Get(sitemapPath, a.serveSitemap)
2020-09-22 15:08:34 +00:00
// Robots.txt - doesn't need cache, because it's too simple
if !a.privateMode {
r.Get("/robots.txt", a.serveRobotsTXT)
2021-02-27 07:31:06 +00:00
} else {
r.Get("/robots.txt", servePrivateRobotsTXT)
}
2020-10-15 18:54:43 +00:00
// Check redirects, then serve 404
r.With(a.cache.cacheMiddleware, a.checkRegexRedirects).NotFound(a.serve404)
r.MethodNotAllowed(a.serveNotAllowed)
2020-12-24 09:09:34 +00:00
return r, nil
2020-07-28 19:17:07 +00:00
}
const blogContextKey requestContextKey = "blog"
const pathContextKey requestContextKey = "httpPath"
2021-03-19 09:10:47 +00:00
var cspDomains = ""
func (a *goBlog) refreshCSPDomains() {
2021-03-19 09:10:47 +00:00
cspDomains = ""
if mp := a.cfg.Micropub.MediaStorage; mp != nil && mp.MediaURL != "" {
2021-02-16 20:27:52 +00:00
if u, err := url.Parse(mp.MediaURL); err == nil {
2021-03-19 09:10:47 +00:00
cspDomains += " " + u.Hostname()
2021-02-16 20:27:52 +00:00
}
}
if len(a.cfg.Server.CSPDomains) > 0 {
cspDomains += " " + strings.Join(a.cfg.Server.CSPDomains, " ")
2021-02-16 20:27:52 +00:00
}
2021-03-19 09:10:47 +00:00
}
func (a *goBlog) securityHeaders(next http.Handler) http.Handler {
a.refreshCSPDomains()
2020-10-16 13:35:38 +00:00
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2021-02-27 07:31:06 +00:00
w.Header().Set("Strict-Transport-Security", "max-age=31536000;")
w.Header().Set("Referrer-Policy", "no-referrer")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
w.Header().Set("X-Xss-Protection", "1; mode=block")
2021-03-19 09:10:47 +00:00
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains)
if a.cfg.Server.Tor && a.torAddress != "" {
w.Header().Set("Onion-Location", fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI))
2021-03-19 09:10:47 +00:00
}
2021-02-27 07:31:06 +00:00
next.ServeHTTP(w, r)
})
}
func noIndexHeader(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Robots-Tag", "noindex")
2020-10-16 13:35:38 +00:00
next.ServeHTTP(w, r)
})
}
type dynamicHandler struct {
router *chi.Mux
mutex sync.RWMutex
initialized bool
}
2021-03-19 10:26:45 +00:00
func (d *dynamicHandler) swapHandler(h *chi.Mux) {
d.mutex.Lock()
d.router = h
d.initialized = true
2021-03-19 10:26:45 +00:00
d.mutex.Unlock()
}
func (d *dynamicHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
2020-11-10 15:40:22 +00:00
// Fix to use Path routing instead of RawPath routing in Chi
r.URL.RawPath = ""
// Serve request
2021-03-19 10:26:45 +00:00
d.mutex.RLock()
for !d.initialized {
time.Sleep(10 * time.Millisecond)
}
2021-03-19 10:26:45 +00:00
router := d.router
d.mutex.RUnlock()
router.ServeHTTP(w, r)
}