mirror of https://github.com/jlelse/GoBlog
Add bodylimits and some other small improvements
parent
5d1eb459ab
commit
02b0fa8e14
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
19
blogroll.go
19
blogroll.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
2
go.mod
|
@ -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
8
go.sum
|
@ -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=
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 := µformatItem{}
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue