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