Add bodylimits and some other small improvements

This commit is contained in:
Jan-Lukas Else 2023-02-04 09:59:04 +01:00
parent 5d1eb459ab
commit 02b0fa8e14
12 changed files with 84 additions and 45 deletions

View File

@ -201,6 +201,7 @@ func (a *goBlog) apCheckActivityPubReply(p *post) {
}
func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) {
// Get blog
blogName := chi.URLParam(r, "blog")
blog, ok := a.cfg.Blogs[blogName]
if !ok || blog == nil {
@ -215,8 +216,7 @@ func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) {
return
}
// Parse activity
limit := int64(10 * 1000 * 1000) // 10 MB
body, err := io.ReadAll(io.LimitReader(r.Body, limit))
body, err := io.ReadAll(r.Body)
if err != nil {
a.serveError(w, r, "Failed to read body", http.StatusBadRequest)
return

View File

@ -10,6 +10,7 @@ import (
"github.com/carlmjohnson/requests"
"github.com/go-chi/chi/v5"
"go.goblog.app/app/pkgs/bodylimit"
)
func (a *goBlog) apRemoteFollow(w http.ResponseWriter, r *http.Request) {
@ -41,13 +42,17 @@ func (a *goBlog) apRemoteFollow(w http.ResponseWriter, r *http.Request) {
Links []*webfingerLinkType `json:"links"`
}
webfinger := &webfingerType{}
err := requests.URL(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s@%s", instance, user, instance)).
Client(a.httpClient).
Handle(func(resp *http.Response) error {
defer resp.Body.Close()
return json.NewDecoder(io.LimitReader(resp.Body, 1000*1000)).Decode(webfinger)
}).
Fetch(r.Context())
pr, pw := io.Pipe()
go func() {
err := requests.
URL(fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s@%s", instance, user, instance)).
Client(a.httpClient).
ToWriter(pw).
Fetch(r.Context())
_ = pw.CloseWithError(err)
}()
err := json.NewDecoder(io.LimitReader(pr, 100*bodylimit.KB)).Decode(webfinger)
_ = pr.CloseWithError(err)
if err != nil {
a.serveError(w, r, "Failed to query webfinger", http.StatusInternalServerError)
return

View File

@ -9,6 +9,7 @@ import (
"strings"
"github.com/pquerna/otp/totp"
"go.goblog.app/app/pkgs/bodylimit"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/contenttype"
)
@ -51,8 +52,6 @@ func (a *goBlog) authMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, r)
return
}
// Remember to close body
defer r.Body.Close()
// Encode original request
headerBuffer, bodyBuffer := bufferpool.Get(), bufferpool.Get()
defer bufferpool.Put(headerBuffer, bodyBuffer)
@ -62,13 +61,14 @@ func (a *goBlog) authMiddleware(next http.Handler) http.Handler {
_ = headerEncoder.Close()
// Encode body
bodyEncoder := base64.NewEncoder(base64.StdEncoding, bodyBuffer)
limit := int64(3 * 1000 * 1000) // 3 MB
limit := 3 * bodylimit.MB
written, _ := io.Copy(bodyEncoder, io.LimitReader(r.Body, limit))
if written == 0 {
// Maybe it's a form
_ = r.ParseForm()
// Encode form
written, _ = io.Copy(bodyEncoder, strings.NewReader(r.Form.Encode()))
sw, _ := io.WriteString(bodyEncoder, r.Form.Encode())
written = int64(sw)
}
bodyEncoder.Close()
if written >= limit {

View File

@ -65,23 +65,27 @@ func (a *goBlog) serveBlogrollExport(w http.ResponseWriter, r *http.Request) {
}
func (a *goBlog) getBlogrollOutlines(blog string) ([]*opml.Outline, error) {
// Get config
config := a.cfg.Blogs[blog].Blogroll
// Check cache
if cache := a.db.loadOutlineCache(blog); cache != nil {
return cache, nil
}
rb := requests.URL(config.Opml).Client(a.httpClient)
// Make request and parse OPML
pr, pw := io.Pipe()
rb := requests.URL(config.Opml).Client(a.httpClient).ToWriter(pw)
if config.AuthHeader != "" && config.AuthValue != "" {
rb.Header(config.AuthHeader, config.AuthValue)
}
var o *opml.OPML
err := rb.Handle(func(r *http.Response) (err error) {
defer r.Body.Close()
o, err = opml.Parse(r.Body)
return
}).Fetch(context.Background())
go func() {
_ = pw.CloseWithError(rb.Fetch(context.Background()))
}()
o, err := opml.Parse(pr)
_ = pr.CloseWithError(err)
if err != nil {
return nil, err
}
// Filter and sort
outlines := o.Outlines
if len(config.Categories) > 0 {
filtered := []*opml.Outline{}
@ -97,6 +101,7 @@ func (a *goBlog) getBlogrollOutlines(blog string) ([]*opml.Outline, error) {
} else {
outlines = sortOutlines(outlines)
}
// Cache
a.db.cacheOutlines(blog, outlines)
return outlines, nil
}

View File

@ -10,6 +10,7 @@ import (
"time"
"github.com/dchest/captcha"
"go.goblog.app/app/pkgs/bodylimit"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/contenttype"
)
@ -40,8 +41,6 @@ func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), captchaSolvedKey, true)))
return
}
// Remember to close body
defer r.Body.Close()
// Get captcha ID
captchaId := ""
if sesCaptchaId, ok := ses.Values["captchaid"]; ok {
@ -64,13 +63,14 @@ func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler {
_ = headerEncoder.Close()
// Encode body
bodyEncoder := base64.NewEncoder(base64.StdEncoding, bodyBuffer)
limit := int64(1000 * 1000) // 1 MB
limit := 3 * bodylimit.MB
written, _ := io.Copy(bodyEncoder, io.LimitReader(r.Body, limit))
if written == 0 {
// Maybe it's a form
_ = r.ParseForm()
// Encode form
written, _ = io.Copy(bodyEncoder, strings.NewReader(r.Form.Encode()))
sw, _ := io.WriteString(bodyEncoder, r.Form.Encode())
written = int64(sw)
}
bodyEncoder.Close()
if written >= limit {

2
go.mod
View File

@ -8,7 +8,7 @@ require (
git.jlel.se/jlelse/goldmark-mark v0.0.0-20210522162520-9788c89266a4
git.jlel.se/jlelse/template-strings v0.0.0-20220211095702-c012e3b5045b
github.com/PuerkitoBio/goquery v1.8.0
github.com/alecthomas/chroma/v2 v2.4.0
github.com/alecthomas/chroma/v2 v2.5.0
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/carlmjohnson/requests v0.23.2

8
go.sum
View File

@ -61,10 +61,10 @@ github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0g
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/assert/v2 v2.2.0 h1:f6L/b7KE2bfA+9O4FL3CM/xJccDEwPVYd5fALBiuwvw=
github.com/alecthomas/chroma/v2 v2.4.0 h1:Loe2ZjT5x3q1bcWwemqyqEi8p11/IV/ncFCeLYDpWC4=
github.com/alecthomas/chroma/v2 v2.4.0/go.mod h1:6kHzqF5O6FUSJzBXW7fXELjb+e+7OXW4UpoPqMO7IBQ=
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
github.com/alecthomas/chroma/v2 v2.5.0 h1:CQCdj1BiBV17sD4Bd32b/Bzuiq/EqoNTrnIhyQAZ+Rk=
github.com/alecthomas/chroma/v2 v2.5.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=

View File

@ -3,6 +3,7 @@ package main
import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"go.goblog.app/app/pkgs/bodylimit"
)
// Login
@ -16,8 +17,8 @@ func (a *goBlog) loginRouter(r chi.Router) {
func (a *goBlog) micropubRouter(r chi.Router) {
r.Use(a.checkIndieAuth)
r.Get("/", a.serveMicropubQuery)
r.Post("/", a.serveMicropubPost)
r.Post(micropubMediaSubPath, a.serveMicropubMedia)
r.With(bodylimit.BodyLimit(10*bodylimit.MB)).Post("/", a.serveMicropubPost)
r.With(bodylimit.BodyLimit(30*bodylimit.MB)).Post(micropubMediaSubPath, a.serveMicropubMedia)
}
// IndieAuth
@ -25,10 +26,10 @@ func (a *goBlog) indieAuthRouter(r chi.Router) {
r.Route(indieAuthPath, func(r chi.Router) {
r.Get("/", a.indieAuthRequest)
r.With(a.authMiddleware).Post("/accept", a.indieAuthAccept)
r.Post("/", a.indieAuthVerificationAuth)
r.Post(indieAuthTokenSubpath, a.indieAuthVerificationToken)
r.With(bodylimit.BodyLimit(100*bodylimit.KB)).Post("/", a.indieAuthVerificationAuth)
r.With(bodylimit.BodyLimit(100*bodylimit.KB)).Post(indieAuthTokenSubpath, a.indieAuthVerificationToken)
r.Get(indieAuthTokenSubpath, a.indieAuthTokenVerification)
r.Post(indieAuthTokenRevocationSubpath, a.indieAuthTokenRevokation)
r.With(bodylimit.BodyLimit(100*bodylimit.KB)).Post(indieAuthTokenRevocationSubpath, a.indieAuthTokenRevokation)
})
r.With(cacheLoggedIn, a.cacheMiddleware).Get("/.well-known/oauth-authorization-server", a.indieAuthMetadata)
}
@ -41,10 +42,10 @@ func (a *goBlog) activityPubRouter(r chi.Router) {
}
if ap := a.cfg.ActivityPub; ap != nil && ap.Enabled {
r.Route("/activitypub", func(r chi.Router) {
r.Post("/inbox/{blog}", a.apHandleInbox)
r.With(bodylimit.BodyLimit(10*bodylimit.MB)).Post("/inbox/{blog}", a.apHandleInbox)
r.With(a.checkActivityStreamsRequest).Get("/followers/{blog}", a.apShowFollowers)
r.With(a.cacheMiddleware).Get("/remote_follow/{blog}", a.apRemoteFollow)
r.Post("/remote_follow/{blog}", a.apRemoteFollow)
r.With(bodylimit.BodyLimit(100*bodylimit.KB)).Post("/remote_follow/{blog}", a.apRemoteFollow)
})
r.Group(func(r chi.Router) {
r.Use(cacheLoggedIn, a.cacheMiddleware)
@ -63,7 +64,7 @@ func (a *goBlog) webmentionsRouter(r chi.Router) {
return
}
// Endpoint
r.Post("/", a.handleWebmention)
r.With(bodylimit.BodyLimit(bodylimit.MB)).Post("/", a.handleWebmention)
// Authenticated routes
r.Group(func(r chi.Router) {
r.Use(a.authMiddleware)
@ -123,7 +124,7 @@ func (a *goBlog) otherRoutesRouter(r chi.Router) {
// Reactions
if a.reactionsEnabled() {
r.Get("/reactions", a.getReactions)
r.Post("/reactions", a.postReaction)
r.With(bodylimit.BodyLimit(100*bodylimit.KB)).Post("/reactions", a.postReaction)
}
}
@ -303,7 +304,7 @@ func (a *goBlog) blogSearchRouter(conf *configBlog) func(r chi.Router) {
middleware.WithValue(pathKey, searchPath),
)
r.Get("/", a.serveSearch)
r.Post("/", a.serveSearch)
r.With(bodylimit.BodyLimit(100*bodylimit.KB)).Post("/", a.serveSearch)
searchResultPath := "/" + searchPlaceholder
r.Get(searchResultPath, a.serveSearchResult)
r.Get(searchResultPath+feedPath, a.serveSearchResult)
@ -377,7 +378,7 @@ func (a *goBlog) blogCommentsRouter(conf *configBlog) func(r chi.Router) {
middleware.WithValue(pathKey, commentsPath),
)
r.With(a.cacheMiddleware, noIndexHeader).Get("/{id:[0-9]+}", a.serveComment)
r.With(a.captchaMiddleware).Post("/", a.createCommentFromRequest)
r.With(a.captchaMiddleware, bodylimit.BodyLimit(bodylimit.MB)).Post("/", a.createCommentFromRequest)
r.Group(func(r chi.Router) {
// Admin
r.Use(a.authMiddleware)
@ -441,7 +442,7 @@ func (a *goBlog) blogContactRouter(conf *configBlog) func(r chi.Router) {
r.Route(contactPath, func(r chi.Router) {
r.Use(a.privateModeHandler, a.cacheMiddleware)
r.Get("/", a.serveContactForm)
r.With(a.captchaMiddleware).Post("/", a.sendContactSubmission)
r.With(a.captchaMiddleware, bodylimit.BodyLimit(bodylimit.MB)).Post("/", a.sendContactSubmission)
})
}
}

View File

@ -101,7 +101,6 @@ func (a *goBlog) getMicropubChannelsMap() []map[string]any {
}
func (a *goBlog) serveMicropubPost(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
blog, _ := a.getBlog(r)
p := &post{Blog: blog}
switch mt, _, _ := mime.ParseMediaType(r.Header.Get(contentType)); mt {
@ -125,7 +124,7 @@ func (a *goBlog) serveMicropubPost(w http.ResponseWriter, r *http.Request) {
a.micropubCreatePostFromForm(w, r, p)
case contenttype.JSON:
parsedMfItem := &microformatItem{}
err := json.NewDecoder(io.LimitReader(r.Body, 10000000)).Decode(parsedMfItem)
err := json.NewDecoder(r.Body).Decode(parsedMfItem)
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return

View File

@ -15,7 +15,6 @@ import (
const micropubMediaSubPath = "/media"
func (a *goBlog) serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
// Check scope
if !a.micropubCheckScope(w, r, "media") {
return

View File

@ -0,0 +1,31 @@
// package bodylimit provides a HTTP middleware that limits the maximum body size of requests
package bodylimit
import "net/http"
const (
// Decimal
KB int64 = 1000
MB = 1000 * KB
GB = 1000 * MB
TB = 1000 * GB
PB = 1000 * TB
// Binary
KiB int64 = 1024
MiB = 1024 * KiB
GiB = 1024 * MiB
TiB = 1024 * GiB
PiB = 1024 * TiB
)
func BodyLimit(n int64) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if n > 0 {
r.Body = http.MaxBytesReader(w, r.Body, n)
}
next.ServeHTTP(w, r)
})
}
}

View File

@ -194,7 +194,6 @@ func (a *goBlog) serveUpdateProfileImage(w http.ResponseWriter, r *http.Request)
}
_, err = io.Copy(dataFile, file)
_ = file.Close()
_ = r.Body.Close()
if err != nil {
a.serveError(w, r, "Failed to save image", http.StatusBadRequest)
return