diff --git a/activityPub.go b/activityPub.go index 7f2e91f..4bfe553 100644 --- a/activityPub.go +++ b/activityPub.go @@ -84,8 +84,7 @@ func apHandleWebfinger(w http.ResponseWriter, r *http.Request) { serveError(w, r, "Blog not found", http.StatusNotFound) return } - w.Header().Set(contentType, "application/jrd+json"+charsetUtf8Suffix) - _ = json.NewEncoder(w).Encode(map[string]interface{}{ + b, _ := json.Marshal(map[string]interface{}{ "subject": "acct:" + name + "@" + appConfig.Server.publicHostname, "links": []map[string]string{ { @@ -95,6 +94,8 @@ func apHandleWebfinger(w http.ResponseWriter, r *http.Request) { }, }, }) + w.Header().Set(contentType, "application/jrd+json"+charsetUtf8Suffix) + _, _ = writeMinified(w, contentTypeJSON, b) } func apHandleInbox(w http.ResponseWriter, r *http.Request) { diff --git a/activityStreams.go b/activityStreams.go index ef830d0..6883908 100644 --- a/activityStreams.go +++ b/activityStreams.go @@ -14,9 +14,11 @@ var asContext = []string{"https://www.w3.org/ns/activitystreams"} func manipulateAsPath(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if lowerAccept := strings.ToLower(r.Header.Get("Accept")); (strings.Contains(lowerAccept, contentTypeAS) || strings.Contains(lowerAccept, "application/ld+json")) && !strings.Contains(lowerAccept, contentTypeHTML) { - // Is ActivityStream, add ".as" to differentiate cache and also trigger as function - r.URL.Path += ".as" + if ap := appConfig.ActivityPub; ap != nil && ap.Enabled { + if lowerAccept := strings.ToLower(r.Header.Get("Accept")); (strings.Contains(lowerAccept, contentTypeAS) || strings.Contains(lowerAccept, "application/ld+json")) && !strings.Contains(lowerAccept, contentTypeHTML) { + // Is ActivityStream, add ".as" to differentiate cache and also trigger as function + r.URL.Path += ".as" + } } next.ServeHTTP(rw, r) }) @@ -68,9 +70,9 @@ type asEndpoints struct { } func (p *post) serveActivityStreams(w http.ResponseWriter) { - // Send JSON - w.Header().Add(contentType, contentTypeASUTF8) - _ = json.NewEncoder(w).Encode(p.toASNote()) + b, _ := json.Marshal(p.toASNote()) + w.Header().Set(contentType, contentTypeASUTF8) + _, _ = writeMinified(w, contentTypeAS, b) } func (p *post) toASNote() *asNote { @@ -126,8 +128,6 @@ func (b *configBlog) serveActivityStreams(blog string, w http.ResponseWriter, r serveError(w, r, "Failed to marshal public key", http.StatusInternalServerError) return } - // Send JSON - w.Header().Add(contentType, contentTypeASUTF8) asBlog := &asPerson{ Context: asContext, Type: "Person", @@ -154,5 +154,7 @@ func (b *configBlog) serveActivityStreams(blog string, w http.ResponseWriter, r URL: appConfig.User.Picture, } } - _ = json.NewEncoder(w).Encode(asBlog) + jb, _ := json.Marshal(asBlog) + w.Header().Set(contentType, contentTypeASUTF8) + _, _ = writeMinified(w, contentTypeAS, jb) } diff --git a/api.go b/api.go deleted file mode 100644 index 16a3361..0000000 --- a/api.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "io/ioutil" - "net/http" - "strings" -) - -// Not tested anymore - -func apiPostCreateHugo(w http.ResponseWriter, r *http.Request) { - blog := r.URL.Query().Get("blog") - path := r.URL.Query().Get("path") - section := r.URL.Query().Get("section") - slug := r.URL.Query().Get("slug") - alias := r.URL.Query().Get("alias") - defer func() { - _ = r.Body.Close() - }() - bodyContent, err := ioutil.ReadAll(r.Body) - if err != nil { - serveError(w, r, err.Error(), http.StatusBadRequest) - return - } - p, aliases, err := parseHugoFile(string(bodyContent)) - if err != nil { - serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } - p.Blog = blog - p.Path = path - p.Section = section - p.Slug = slug - err = p.create() - if err != nil { - serveError(w, r, err.Error(), http.StatusBadRequest) - return - } - aliases = append(aliases, alias) - for i, alias := range aliases { - // Fix relativ paths - if !strings.HasPrefix(alias, "/") { - splittedPostPath := strings.Split(p.Path, "/") - alias = strings.TrimSuffix(p.Path, splittedPostPath[len(splittedPostPath)-1]) + alias - } - alias = strings.TrimSuffix(alias, "/") - if alias == p.Path { - alias = "" - } - aliases[i] = alias - } - if len(aliases) > 0 { - p.Parameters["aliases"] = aliases - err = p.replace(p.Path, p.Status) - if err != nil { - serveError(w, r, err.Error(), http.StatusBadRequest) - return - } - } - http.Redirect(w, r, p.fullURL(), http.StatusCreated) -} diff --git a/config.go b/config.go index ef1ef77..e855019 100644 --- a/config.go +++ b/config.go @@ -16,7 +16,6 @@ type config struct { Blogs map[string]*configBlog `mapstructure:"blogs"` User *configUser `mapstructure:"user"` Hooks *configHooks `mapstructure:"hooks"` - Hugo *configHugo `mapstructure:"hugo"` Micropub *configMicropub `mapstructure:"micropub"` PathRedirects []*configRegexRedirect `mapstructure:"pathRedirects"` ActivityPub *configActivityPub `mapstructure:"activityPub"` @@ -151,15 +150,6 @@ type configHooks struct { PostDelete []string `mapstructure:"postdelete"` } -type configHugo struct { - Frontmatter []*frontmatter `mapstructure:"frontmatter"` -} - -type frontmatter struct { - Meta string `mapstructure:"meta"` - Parameter string `mapstructure:"parameter"` -} - type configMicropub struct { CategoryParam string `mapstructure:"categoryParam"` ReplyParam string `mapstructure:"replyParam"` @@ -223,7 +213,6 @@ func initConfig() error { viper.SetDefault("user.nick", "admin") viper.SetDefault("user.password", "secret") viper.SetDefault("hooks.shell", "/bin/bash") - viper.SetDefault("hugo.frontmatter", []*frontmatter{{Meta: "title", Parameter: "title"}, {Meta: "tags", Parameter: "tags"}}) viper.SetDefault("micropub.categoryParam", "tags") viper.SetDefault("micropub.replyParam", "replylink") viper.SetDefault("micropub.likeParam", "likelink") diff --git a/editor.go b/editor.go index 856bbbd..8bf4633 100644 --- a/editor.go +++ b/editor.go @@ -3,7 +3,7 @@ package main import ( "bytes" "encoding/json" - "io/ioutil" + "io" "net/http" "net/http/httptest" "net/url" @@ -103,6 +103,6 @@ func editorMicropubPost(w http.ResponseWriter, r *http.Request, media bool) { return } w.WriteHeader(result.StatusCode) - body, _ := ioutil.ReadAll(result.Body) - _, _ = w.Write(body) + _, _ = io.Copy(w, result.Body) + _ = result.Body.Close() } diff --git a/feeds.go b/feeds.go index b392c48..707de38 100644 --- a/feeds.go +++ b/feeds.go @@ -66,16 +66,17 @@ func generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Reques }) } var err error + var feedString, feedMediaType string switch f { case rssFeed: - w.Header().Set(contentType, "application/rss+xml; charset=utf-8") - err = feed.WriteRss(w) + feedMediaType = contentTypeRSS + feedString, err = feed.ToRss() case atomFeed: - w.Header().Set(contentType, "application/atom+xml; charset=utf-8") - err = feed.WriteAtom(w) + feedMediaType = contentTypeATOM + feedString, err = feed.ToAtom() case jsonFeed: - w.Header().Set(contentType, "application/feed+json; charset=utf-8") - err = feed.WriteJSON(w) + feedMediaType = contentTypeJSONFeed + feedString, err = feed.ToJSON() default: return } @@ -84,4 +85,6 @@ func generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Reques serveError(w, r, err.Error(), http.StatusInternalServerError) return } + w.Header().Set(contentType, feedMediaType+charsetUtf8Suffix) + _, _ = writeMinified(w, feedMediaType, []byte(feedString)) } diff --git a/go.mod b/go.mod index 8fa0e33..c02ae6b 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,6 @@ require ( github.com/gorilla/feeds v1.1.1 github.com/gorilla/handlers v1.5.1 github.com/hashicorp/golang-lru v0.5.4 - github.com/jeremywohl/flatten v1.0.1 github.com/jonboulle/clockwork v0.2.2 // indirect github.com/joncrlsn/dque v0.0.0-20200702023911-3e80e3146ce5 github.com/klauspost/cpuid v1.3.1 // indirect @@ -43,8 +42,7 @@ require ( github.com/spf13/cast v1.3.1 github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/viper v1.7.1 - github.com/tdewolff/minify/v2 v2.9.12 - github.com/tdewolff/parse/v2 v2.5.10 // indirect + github.com/tdewolff/minify/v2 v2.9.13 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2 github.com/yuin/goldmark v1.3.2 diff --git a/go.sum b/go.sum index c5f80a9..c5c19e5 100644 --- a/go.sum +++ b/go.sum @@ -142,8 +142,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs= -github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -284,10 +282,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tdewolff/minify/v2 v2.9.12 h1:BKnbg3kcAuTLAhDeF3vSUxjvyj5lBRxaBvgxGCVLRRY= -github.com/tdewolff/minify/v2 v2.9.12/go.mod h1:yuntVVAFuGyi9VmiRoUqAYEQnFCGO929ytj2ITMZuB8= -github.com/tdewolff/parse/v2 v2.5.9 h1:9wCXRT3OcgYDNatgU+HUTOoGhE9WcnY5UxLNoJUe1yw= -github.com/tdewolff/parse/v2 v2.5.9/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= +github.com/tdewolff/minify/v2 v2.9.13 h1:RrwQhgGoYBhKN/ezStGB+crU64wPK1ZE5Jmkl63lif0= +github.com/tdewolff/minify/v2 v2.9.13/go.mod h1:faNOp+awAoo+fhFHD+NAkBOaXBAvJI2X2SDERGKnARo= github.com/tdewolff/parse/v2 v2.5.10 h1:vj35n+ljq8LuYUx436s4qB18wuwP7thrLv+t1syE39M= github.com/tdewolff/parse/v2 v2.5.10/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= diff --git a/http.go b/http.go index 86e1cd6..6a012ef 100644 --- a/http.go +++ b/http.go @@ -23,6 +23,9 @@ const ( 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 contentTypeJSONUTF8 = contentTypeJSON + charsetUtf8Suffix @@ -99,15 +102,9 @@ func buildHandler() (http.Handler, error) { r.Mount("/debug", middleware.Profiler()) } - // API - r.Route("/api", func(apiRouter chi.Router) { - apiRouter.Use(middleware.NoCache, authMiddleware) - apiRouter.Post("/hugo", apiPostCreateHugo) - }) - // Micropub r.Route(micropubPath, func(mpRouter chi.Router) { - mpRouter.Use(checkIndieAuth, middleware.NoCache, minifier.Middleware) + mpRouter.Use(checkIndieAuth) mpRouter.Get("/", serveMicropubQuery) mpRouter.Post("/", serveMicropubPost) mpRouter.Post(micropubMediaSubPath, serveMicropubMedia) @@ -115,7 +112,6 @@ func buildHandler() (http.Handler, error) { // IndieAuth r.Route("/indieauth", func(indieauthRouter chi.Router) { - indieauthRouter.Use(middleware.NoCache, minifier.Middleware) indieauthRouter.Get("/", indieAuthRequest) indieauthRouter.With(authMiddleware).Post("/accept", indieAuthAccept) indieauthRouter.Post("/", indieAuthVerification) @@ -124,23 +120,26 @@ func buildHandler() (http.Handler, error) { }) // ActivityPub and stuff - if appConfig.ActivityPub.Enabled { + if ap := appConfig.ActivityPub; ap != nil && ap.Enabled { r.Post("/activitypub/inbox/{blog}", apHandleInbox) r.Post("/activitypub/{blog}/inbox", apHandleInbox) - r.Get("/.well-known/webfinger", apHandleWebfinger) + r.With(cacheMiddleware).Get("/.well-known/webfinger", apHandleWebfinger) r.With(cacheMiddleware).Get("/.well-known/host-meta", handleWellKnownHostMeta) - r.With(cacheMiddleware, minifier.Middleware).Get("/.well-known/nodeinfo", serveNodeInfoDiscover) - r.With(cacheMiddleware, minifier.Middleware).Get("/nodeinfo", serveNodeInfo) + r.With(cacheMiddleware).Get("/.well-known/nodeinfo", serveNodeInfoDiscover) + r.With(cacheMiddleware).Get("/nodeinfo", serveNodeInfo) } // Webmentions r.Route(webmentionPath, func(webmentionRouter chi.Router) { - webmentionRouter.Use(middleware.NoCache) webmentionRouter.Post("/", handleWebmention) - webmentionRouter.With(minifier.Middleware, authMiddleware).Get("/", webmentionAdmin) - webmentionRouter.With(minifier.Middleware, authMiddleware).Get(paginationPath, webmentionAdmin) - webmentionRouter.With(authMiddleware).Post("/delete", webmentionAdminDelete) - webmentionRouter.With(authMiddleware).Post("/approve", webmentionAdminApprove) + webmentionRouter.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) + }) }) // Posts @@ -148,39 +147,36 @@ func buildHandler() (http.Handler, error) { if err != nil { return nil, err } - var postMW []func(http.Handler) http.Handler - if appConfig.ActivityPub.Enabled { - postMW = []func(http.Handler) http.Handler{manipulateAsPath, cacheMiddleware, minifier.Middleware} - } else { - postMW = []func(http.Handler) http.Handler{cacheMiddleware, minifier.Middleware} - } - for _, path := range pp { - if path != "" { - r.With(postMW...).Get(path, servePost) + r.Group(func(r chi.Router) { + r.Use(manipulateAsPath, cacheMiddleware) + for _, path := range pp { + r.Get(path, servePost) } - } + }) // Drafts dp, err := allPostPaths(statusDraft) if err != nil { return nil, err } - for _, path := range dp { - if path != "" { - r.With(middleware.NoCache, minifier.Middleware, authMiddleware).Get(path, servePost) + r.Group(func(r chi.Router) { + r.Use(authMiddleware, cacheMiddleware) + for _, path := range dp { + r.Get(path, servePost) } - } + }) // Post aliases allPostAliases, err := allPostAliases() if err != nil { return nil, err } - for _, path := range allPostAliases { - if path != "" { - r.With(cacheMiddleware).Get(path, servePostAlias) + r.Group(func(r chi.Router) { + r.Use(cacheMiddleware) + for _, path := range allPostAliases { + r.Get(path, servePostAlias) } - } + }) // Assets for _, path := range allAssetPaths() { @@ -209,60 +205,79 @@ func buildHandler() (http.Handler, error) { blogPath = "" } - // Indexes, Feeds - for _, section := range blogConfig.Sections { - if section.Name != "" { - path := blogPath + "/" + section.Name - handler := serveSection(blog, path, section) - r.With(cacheMiddleware, minifier.Middleware).Get(path, handler) - r.With(cacheMiddleware, minifier.Middleware).Get(path+feedPath, handler) - r.With(cacheMiddleware, minifier.Middleware).Get(path+paginationPath, handler) - } - } + r.Group(func(r chi.Router) { + }) + + // Sections + r.Group(func(r chi.Router) { + r.Use(cacheMiddleware) + for _, section := range blogConfig.Sections { + if section.Name != "" { + secPath := blogPath + "/" + section.Name + handler := serveSection(blog, secPath, section) + r.Get(secPath, handler) + r.Get(secPath+feedPath, handler) + r.Get(secPath+paginationPath, handler) + } + } + }) + + // Taxonomies for _, taxonomy := range blogConfig.Taxonomies { if taxonomy.Name != "" { - path := blogPath + "/" + taxonomy.Name - r.With(cacheMiddleware, minifier.Middleware).Get(path, serveTaxonomy(blog, taxonomy)) - values, err := allTaxonomyValues(blog, taxonomy.Name) + taxPath := blogPath + "/" + taxonomy.Name + taxValues, err := allTaxonomyValues(blog, taxonomy.Name) if err != nil { return nil, err } - for _, tv := range values { - vPath := path + "/" + urlize(tv) - handler := serveTaxonomyValue(blog, vPath, taxonomy, tv) - r.With(cacheMiddleware, minifier.Middleware).Get(vPath, handler) - r.With(cacheMiddleware, minifier.Middleware).Get(vPath+feedPath, handler) - r.With(cacheMiddleware, minifier.Middleware).Get(vPath+paginationPath, handler) - } + r.Group(func(r chi.Router) { + r.Use(cacheMiddleware) + r.Get(taxPath, serveTaxonomy(blog, taxonomy)) + for _, tv := range taxValues { + vPath := taxPath + "/" + urlize(tv) + handler := serveTaxonomyValue(blog, vPath, taxonomy, tv) + r.Get(vPath, handler) + r.Get(vPath+feedPath, handler) + r.Get(vPath+paginationPath, handler) + } + }) + } } // Photos if blogConfig.Photos != nil && blogConfig.Photos.Enabled { - photoPath := blogPath + blogConfig.Photos.Path - handler := servePhotos(blog, photoPath) - r.With(cacheMiddleware, minifier.Middleware).Get(photoPath, handler) - r.With(cacheMiddleware, minifier.Middleware).Get(photoPath+paginationPath, handler) + r.Group(func(r chi.Router) { + r.Use(cacheMiddleware) + photoPath := blogPath + blogConfig.Photos.Path + handler := servePhotos(blog, photoPath) + r.Get(photoPath, handler) + r.Get(photoPath+feedPath, handler) + r.Get(photoPath+paginationPath, handler) + }) } // Search if blogConfig.Search != nil && blogConfig.Search.Enabled { - searchPath := blogPath + blogConfig.Search.Path - handler := serveSearch(blog, searchPath) - r.With(cacheMiddleware, minifier.Middleware).Get(searchPath, handler) - r.With(cacheMiddleware, minifier.Middleware).Post(searchPath, handler) - searchResultPath := searchPath + "/" + searchPlaceholder - resultHandler := serveSearchResults(blog, searchResultPath) - r.With(cacheMiddleware, minifier.Middleware).Get(searchResultPath, resultHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(searchResultPath+feedPath, resultHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(searchResultPath+paginationPath, resultHandler) + r.Group(func(r chi.Router) { + r.Use(cacheMiddleware) + searchPath := blogPath + blogConfig.Search.Path + handler := serveSearch(blog, searchPath) + r.Get(searchPath, handler) + r.Post(searchPath, handler) + searchResultPath := searchPath + "/" + searchPlaceholder + resultHandler := serveSearchResults(blog, searchResultPath) + r.Get(searchResultPath, resultHandler) + r.Get(searchResultPath+feedPath, resultHandler) + r.Get(searchResultPath+paginationPath, resultHandler) + }) } // Stats if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled { statsPath := blogPath + blogConfig.BlogStats.Path - r.With(cacheMiddleware, minifier.Middleware).Get(statsPath, serveBlogStats(blog, statsPath)) + r.With(cacheMiddleware).Get(statsPath, serveBlogStats(blog, statsPath)) } // Year / month archives @@ -270,60 +285,57 @@ func buildHandler() (http.Handler, error) { if err != nil { return nil, err } - for _, d := range dates { - // Year - yearPath := blogPath + "/" + fmt.Sprintf("%0004d", d.year) - yearHandler := serveDate(blog, yearPath, d.year, 0, 0) - r.With(cacheMiddleware, minifier.Middleware).Get(yearPath, yearHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(yearPath+feedPath, yearHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(yearPath+paginationPath, yearHandler) - // Specific month - monthPath := yearPath + "/" + fmt.Sprintf("%02d", d.month) - monthHandler := serveDate(blog, monthPath, d.year, d.month, 0) - r.With(cacheMiddleware, minifier.Middleware).Get(monthPath, monthHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(monthPath+feedPath, monthHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(monthPath+paginationPath, monthHandler) - // Specific day - dayPath := monthPath + "/" + fmt.Sprintf("%02d", d.day) - dayHandler := serveDate(blog, monthPath, d.year, d.month, d.day) - r.With(cacheMiddleware, minifier.Middleware).Get(dayPath, dayHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(dayPath+feedPath, dayHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(dayPath+paginationPath, dayHandler) - // Generic month - genericMonthPath := blogPath + "/x/" + fmt.Sprintf("%02d", d.month) - genericMonthHandler := serveDate(blog, genericMonthPath, 0, d.month, 0) - r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthPath, genericMonthHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthPath+feedPath, genericMonthHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthPath+paginationPath, genericMonthHandler) - // Specific day - genericMonthDayPath := genericMonthPath + "/" + fmt.Sprintf("%02d", d.day) - genericMonthDayHandler := serveDate(blog, genericMonthDayPath, 0, d.month, d.day) - r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthDayPath, genericMonthDayHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthDayPath+feedPath, genericMonthDayHandler) - r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthDayPath+paginationPath, genericMonthDayHandler) - } + r.Group(func(r chi.Router) { + r.Use(cacheMiddleware) + for _, d := range dates { + // Year + yearPath := blogPath + "/" + fmt.Sprintf("%0004d", d.year) + yearHandler := serveDate(blog, yearPath, d.year, 0, 0) + r.Get(yearPath, yearHandler) + r.Get(yearPath+feedPath, yearHandler) + r.Get(yearPath+paginationPath, yearHandler) + // Specific month + monthPath := yearPath + "/" + fmt.Sprintf("%02d", d.month) + monthHandler := serveDate(blog, monthPath, d.year, d.month, 0) + r.Get(monthPath, monthHandler) + r.Get(monthPath+feedPath, monthHandler) + r.Get(monthPath+paginationPath, monthHandler) + // Specific day + dayPath := monthPath + "/" + fmt.Sprintf("%02d", d.day) + dayHandler := serveDate(blog, monthPath, d.year, d.month, d.day) + r.Get(dayPath, dayHandler) + r.Get(dayPath+feedPath, dayHandler) + r.Get(dayPath+paginationPath, dayHandler) + // Generic month + genericMonthPath := blogPath + "/x/" + fmt.Sprintf("%02d", d.month) + genericMonthHandler := serveDate(blog, genericMonthPath, 0, d.month, 0) + r.Get(genericMonthPath, genericMonthHandler) + r.Get(genericMonthPath+feedPath, genericMonthHandler) + r.Get(genericMonthPath+paginationPath, genericMonthHandler) + // Specific day + genericMonthDayPath := genericMonthPath + "/" + fmt.Sprintf("%02d", d.day) + genericMonthDayHandler := serveDate(blog, genericMonthDayPath, 0, d.month, d.day) + r.Get(genericMonthDayPath, genericMonthDayHandler) + r.Get(genericMonthDayPath+feedPath, genericMonthDayHandler) + r.Get(genericMonthDayPath+paginationPath, genericMonthDayHandler) + } + }) // Blog if !blogConfig.PostAsHome { - var mw []func(http.Handler) http.Handler - if appConfig.ActivityPub.Enabled { - mw = []func(http.Handler) http.Handler{manipulateAsPath, cacheMiddleware, minifier.Middleware} - } else { - mw = []func(http.Handler) http.Handler{cacheMiddleware, minifier.Middleware} - } handler := serveHome(blog, blogPath) - r.With(mw...).Get(fullBlogPath, handler) - r.With(cacheMiddleware, minifier.Middleware).Get(fullBlogPath+feedPath, handler) - r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+paginationPath, handler) + r.With(manipulateAsPath, cacheMiddleware).Get(fullBlogPath, handler) + r.With(cacheMiddleware).Get(fullBlogPath+feedPath, handler) + r.With(cacheMiddleware).Get(blogPath+paginationPath, handler) } // Custom pages for _, cp := range blogConfig.CustomPages { handler := serveCustomPage(blogConfig, cp) if cp.Cache { - r.With(cacheMiddleware, minifier.Middleware).Get(cp.Path, handler) + r.With(cacheMiddleware).Get(cp.Path, handler) } else { - r.With(minifier.Middleware).Get(cp.Path, handler) + r.Get(cp.Path, handler) } } @@ -338,7 +350,7 @@ func buildHandler() (http.Handler, error) { // Editor r.Route(blogPath+"/editor", func(mpRouter chi.Router) { - mpRouter.Use(middleware.NoCache, minifier.Middleware, authMiddleware) + mpRouter.Use(authMiddleware) mpRouter.Get("/", serveEditor(blog)) mpRouter.Post("/", serveEditorPost(blog)) }) @@ -347,25 +359,28 @@ func buildHandler() (http.Handler, error) { if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled { commentsPath := blogPath + "/comment" r.Route(commentsPath, func(cr chi.Router) { - cr.With(cacheMiddleware, minifier.Middleware).Get("/{id:[0-9]+}", serveComment(blog)) + cr.With(cacheMiddleware).Get("/{id:[0-9]+}", serveComment(blog)) cr.With(captchaMiddleware).Post("/", createComment(blog, commentsPath)) // Admin - cr.With(minifier.Middleware, authMiddleware).Get("/", commentsAdmin) - cr.With(authMiddleware).Post("/delete", commentsAdminDelete) + cr.Group(func(r chi.Router) { + r.Use(authMiddleware) + r.Get("/", commentsAdmin) + r.Post("/delete", commentsAdminDelete) + }) }) } } // Sitemap - r.With(cacheMiddleware, minifier.Middleware).Get(sitemapPath, serveSitemap) + r.With(cacheMiddleware).Get(sitemapPath, serveSitemap) // Robots.txt - doesn't need cache, because it's too simple r.Get("/robots.txt", serveRobotsTXT) // Check redirects, then serve 404 - r.With(cacheMiddleware, checkRegexRedirects, minifier.Middleware).NotFound(serve404) + r.With(cacheMiddleware, checkRegexRedirects).NotFound(serve404) - r.With(minifier.Middleware).MethodNotAllowed(func(rw http.ResponseWriter, r *http.Request) { + r.MethodNotAllowed(func(rw http.ResponseWriter, r *http.Request) { serveError(rw, r, "", http.StatusMethodNotAllowed) }) diff --git a/hugo.go b/hugo.go deleted file mode 100644 index ff7a01e..0000000 --- a/hugo.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "strconv" - "strings" - - "github.com/jeremywohl/flatten" - "github.com/spf13/cast" - "gopkg.in/yaml.v3" -) - -func parseHugoFile(fileContent string) (p *post, aliases []string, e error) { - frontmatterSep := "---\n" - frontmatter := "" - if split := strings.Split(fileContent, frontmatterSep); len(split) > 2 { - frontmatter = split[1] - } - p = &post{ - Content: strings.TrimPrefix(fileContent, frontmatterSep+frontmatter+frontmatterSep), - Parameters: map[string][]string{}, - } - // Parse frontmatter - meta := map[string]interface{}{} - err := yaml.Unmarshal([]byte(frontmatter), &meta) - if err != nil { - return nil, nil, err - } - flat, err := flatten.Flatten(meta, "", flatten.DotStyle) - if err != nil { - return nil, nil, err - } - // Read dates - p.Published = cast.ToString(flat["date"]) - p.Updated = cast.ToString(flat["lastmod"]) - // Read parameters - for _, fm := range appConfig.Hugo.Frontmatter { - var values []string - for fk, value := range flat { - if strings.HasPrefix(fk, fm.Meta) { - trimmed := strings.TrimPrefix(fk, fm.Meta) - if len(trimmed) == 0 { - values = append(values, cast.ToString(value)) - } else { - trimmed = strings.TrimPrefix(trimmed, ".") - if _, e := strconv.Atoi(trimmed); e == nil { - values = append(values, cast.ToString(value)) - } - } - } - } - if len(values) > 0 { - p.Parameters[fm.Parameter] = values - } - } - // Parse redirects - for fk, value := range flat { - if strings.HasPrefix(fk, "aliases") { - trimmed := strings.TrimPrefix(fk, "aliases") - if len(trimmed) == 0 { - aliases = append(aliases, cast.ToString(value)) - } else { - trimmed = strings.TrimPrefix(trimmed, ".") - if _, e := strconv.Atoi(trimmed); e == nil { - aliases = append(aliases, cast.ToString(value)) - } - } - } - } - // Return post - return p, aliases, nil -} diff --git a/indieAuthServer.go b/indieAuthServer.go index 739dbb9..3f2ad2d 100644 --- a/indieAuthServer.go +++ b/indieAuthServer.go @@ -134,16 +134,11 @@ func indieAuthVerification(w http.ResponseWriter, r *http.Request) { serveError(w, r, "Authentication not valid", http.StatusForbidden) return } - res := &tokenResponse{ + b, _ := json.Marshal(tokenResponse{ Me: appConfig.Server.PublicAddress, - } - w.Header().Add(contentType, contentTypeJSONUTF8) - err = json.NewEncoder(w).Encode(res) - if err != nil { - w.Header().Del(contentType) - serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } + }) + w.Header().Set(contentType, contentTypeJSONUTF8) + _, _ = writeMinified(w, contentTypeJSON, b) } func indieAuthToken(w http.ResponseWriter, r *http.Request) { @@ -159,13 +154,9 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) { Me: appConfig.Server.PublicAddress, ClientID: data.ClientID, } - w.Header().Add(contentType, contentTypeJSONUTF8) - err = json.NewEncoder(w).Encode(res) - if err != nil { - w.Header().Del(contentType) - serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } + b, _ := json.Marshal(res) + w.Header().Set(contentType, contentTypeJSONUTF8) + _, _ = writeMinified(w, contentTypeJSON, b) return } else if r.Method == http.MethodPost { if err := r.ParseForm(); err != nil { @@ -216,13 +207,9 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) { Scope: strings.Join(data.Scopes, " "), Me: appConfig.Server.PublicAddress, } - w.Header().Add(contentType, contentTypeJSONUTF8) - err = json.NewEncoder(w).Encode(res) - if err != nil { - w.Header().Del(contentType) - serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } + b, _ := json.Marshal(res) + w.Header().Set(contentType, contentTypeJSONUTF8) + _, _ = writeMinified(w, contentTypeJSON, b) return } serveError(w, r, "", http.StatusBadRequest) diff --git a/micropub.go b/micropub.go index 5cec322..62b8159 100644 --- a/micropub.go +++ b/micropub.go @@ -25,11 +25,12 @@ type micropubConfig struct { func serveMicropubQuery(w http.ResponseWriter, r *http.Request) { switch r.URL.Query().Get("q") { case "config": - w.Header().Add(contentType, contentTypeJSONUTF8) + w.Header().Set(contentType, contentTypeJSONUTF8) w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(µpubConfig{ + b, _ := json.Marshal(µpubConfig{ MediaEndpoint: appConfig.Server.PublicAddress + micropubPath + micropubMediaSubPath, }) + _, _ = writeMinified(w, contentTypeJSON, b) case "source": var mf interface{} if urlString := r.URL.Query().Get("url"); urlString != "" { @@ -61,9 +62,10 @@ func serveMicropubQuery(w http.ResponseWriter, r *http.Request) { } mf = list } - w.Header().Add(contentType, contentTypeJSONUTF8) + w.Header().Set(contentType, contentTypeJSONUTF8) w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(mf) + b, _ := json.Marshal(mf) + _, _ = writeMinified(w, contentTypeJSON, b) case "category": allCategories := []string{} for blog := range appConfig.Blogs { @@ -74,11 +76,12 @@ func serveMicropubQuery(w http.ResponseWriter, r *http.Request) { } allCategories = append(allCategories, values...) } - w.Header().Add(contentType, contentTypeJSONUTF8) + w.Header().Set(contentType, contentTypeJSONUTF8) w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]interface{}{ + b, _ := json.Marshal(map[string]interface{}{ "categories": allCategories, }) + _, _ = writeMinified(w, contentTypeJSON, b) default: serve404(w, r) } diff --git a/minify.go b/minify.go index 4b35b52..a42eb28 100644 --- a/minify.go +++ b/minify.go @@ -1,6 +1,8 @@ package main import ( + "io" + "github.com/tdewolff/minify/v2" mCss "github.com/tdewolff/minify/v2/css" mHtml "github.com/tdewolff/minify/v2/html" @@ -17,7 +19,14 @@ func initMinify() { minifier.AddFunc("text/css", mCss.Minify) minifier.AddFunc("text/xml", mXml.Minify) minifier.AddFunc("application/javascript", mJs.Minify) - minifier.AddFunc("application/rss+xml", mXml.Minify) - minifier.AddFunc("application/atom+xml", mXml.Minify) - minifier.AddFunc("application/feed+json", mJson.Minify) + minifier.AddFunc(contentTypeRSS, mXml.Minify) + minifier.AddFunc(contentTypeATOM, mXml.Minify) + minifier.AddFunc(contentTypeJSONFeed, mJson.Minify) + minifier.AddFunc(contentTypeAS, mJson.Minify) +} + +func writeMinified(w io.Writer, mediatype string, b []byte) (int, error) { + mw := minifier.Writer(mediatype, w) + defer func() { mw.Close() }() + return mw.Write(b) } diff --git a/nodeinfo.go b/nodeinfo.go index ad8875d..c28892e 100644 --- a/nodeinfo.go +++ b/nodeinfo.go @@ -6,24 +6,23 @@ import ( ) func serveNodeInfoDiscover(w http.ResponseWriter, r *http.Request) { - w.Header().Set(contentType, contentTypeJSONUTF8) - nid := map[string]interface{}{ + b, _ := json.Marshal(map[string]interface{}{ "links": []map[string]interface{}{ { "href": appConfig.Server.PublicAddress + "/nodeinfo", "rel": "http://nodeinfo.diaspora.software/ns/schema/2.1", }, }, - } - _ = json.NewEncoder(w).Encode(&nid) + }) + w.Header().Set(contentType, contentTypeJSONUTF8) + _, _ = writeMinified(w, contentTypeJSON, b) } func serveNodeInfo(w http.ResponseWriter, r *http.Request) { - w.Header().Set(contentType, contentTypeJSONUTF8) localPosts, _ := countPosts(&postsRequestConfig{ status: statusPublished, }) - nid := map[string]interface{}{ + b, _ := json.Marshal(map[string]interface{}{ "version": "2.1", "software": map[string]interface{}{ "name": "goblog", @@ -41,6 +40,7 @@ func serveNodeInfo(w http.ResponseWriter, r *http.Request) { "webmention", }, "metadata": map[string]interface{}{}, - } - _ = json.NewEncoder(w).Encode(&nid) + }) + w.Header().Set(contentType, contentTypeJSONUTF8) + _, _ = writeMinified(w, contentTypeJSON, b) } diff --git a/postAliases.go b/postAliases.go index e135cf5..b230253 100644 --- a/postAliases.go +++ b/postAliases.go @@ -14,7 +14,9 @@ func allPostAliases() ([]string, error) { for rows.Next() { var path string _ = rows.Scan(&path) - aliases = append(aliases, path) + if path != "" { + aliases = append(aliases, path) + } } return aliases, nil } diff --git a/postsDb.go b/postsDb.go index 54ab48b..eec73ac 100644 --- a/postsDb.go +++ b/postsDb.go @@ -396,7 +396,9 @@ func allPostPaths(status postStatus) ([]string, error) { for rows.Next() { var path string _ = rows.Scan(&path) - postPaths = append(postPaths, path) + if path != "" { + postPaths = append(postPaths, path) + } } return postPaths, nil } diff --git a/render.go b/render.go index 59f9844..935c692 100644 --- a/render.go +++ b/render.go @@ -264,8 +264,8 @@ func render(w http.ResponseWriter, template string, data *renderData) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - // Set content type (needed for minification middleware + // Set content type w.Header().Set(contentType, contentTypeHTMLUTF8) // Write buffered response - _, _ = w.Write(buffer.Bytes()) + _, _ = writeMinified(w, contentTypeHTML, buffer.Bytes()) } diff --git a/sitemap.go b/sitemap.go index ff830b9..0a9a376 100644 --- a/sitemap.go +++ b/sitemap.go @@ -35,5 +35,5 @@ func serveSitemap(w http.ResponseWriter, r *http.Request) { } sm.Add(item) } - _, _ = sm.WriteTo(w) + _, _ = sm.WriteTo(w) // Already minified } diff --git a/templateAssets.go b/templateAssets.go index 5b317da..4a1ab1b 100644 --- a/templateAssets.go +++ b/templateAssets.go @@ -98,6 +98,6 @@ func serveAsset(w http.ResponseWriter, r *http.Request) { return } w.Header().Set("Cache-Control", "public,max-age=31536000,immutable") - w.Header().Set(contentType, af.contentType) + w.Header().Set(contentType, af.contentType+charsetUtf8Suffix) _, _ = w.Write(af.body) }