Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion modules/web/routemock.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,15 @@ func RouterMockPoint(pointName string) func(next http.Handler) http.Handler {
//
// Then the mock function will be executed as a middleware at the mock point.
// It only takes effect in testing mode (setting.IsInTesting == true).
func RouteMock(pointName string, h any) {
func RouteMock(pointName string, h any) func() {
if _, ok := routeMockPoints[pointName]; !ok {
panic("route mock point not found: " + pointName)
}
old := routeMockPoints[pointName]
routeMockPoints[pointName] = toHandlerProvider(h)
return func() {
routeMockPoints[pointName] = old
}
}

// RouteMockReset resets all mock points (no mock anymore)
Expand Down
2 changes: 1 addition & 1 deletion modules/web/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func NewRouter() *Router {
// Use supports two middlewares
func (r *Router) Use(middlewares ...any) {
for _, m := range middlewares {
if m != nil {
if !isNilOrFuncNil(m) {
r.chiRouter.Use(toHandlerProvider(m))
}
}
Expand Down
7 changes: 5 additions & 2 deletions routers/common/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,11 @@ func RequestContextHandler() func(h http.Handler) http.Handler {
req = req.WithContext(cache.WithCacheContext(ctx))
ds.SetContextValue(httplib.RequestContextKey, req)
ds.AddCleanUp(func() {
if req.MultipartForm != nil {
_ = req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
// The req in context might have changed due to the new req.WithContext calls
// For example: in NewBaseContext, a new "req" with context is created, and the multipart-form is parsed there.
ctxReq := ds.GetContextValue(httplib.RequestContextKey).(*http.Request)
if ctxReq.MultipartForm != nil {
_ = ctxReq.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
}
})
next.ServeHTTP(respWriter, req)
Expand Down
6 changes: 4 additions & 2 deletions routers/web/auth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,11 @@ type LinkAccountData struct {
GothUser goth.User
}

func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData {
func init() {
gob.Register(LinkAccountData{})
}

func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData {
v, ok := ctx.Session.Get("linkAccountData").(LinkAccountData)
if !ok {
return nil
Expand All @@ -287,7 +290,6 @@ func oauth2GetLinkAccountData(ctx *context.Context) *LinkAccountData {
}

func Oauth2SetLinkAccountData(ctx *context.Context, linkAccountData LinkAccountData) error {
gob.Register(LinkAccountData{})
return updateSession(ctx, nil, map[string]any{
"linkAccountData": linkAccountData,
})
Expand Down
4 changes: 3 additions & 1 deletion routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ func ctxDataSet(args ...any) func(ctx *context.Context) {
}
}

const RouterMockPointBeforeWebRoutes = "before-web-routes"

// Routes returns all web routes
func Routes() *web.Router {
routes := web.NewRouter()
Expand Down Expand Up @@ -285,7 +287,7 @@ func Routes() *web.Router {

webRoutes := web.NewRouter()
webRoutes.Use(mid...)
webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive(), common.QoS())
webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive(), common.QoS(), web.RouterMockPoint(RouterMockPointBeforeWebRoutes))
routes.Mount("", webRoutes)
return routes
}
Expand Down
4 changes: 3 additions & 1 deletion services/context/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ type Base struct {
Locale translation.Locale
}

var ParseMultipartFormMaxMemory = int64(32 << 20)

func (b *Base) ParseMultipartForm() bool {
err := b.Req.ParseMultipartForm(32 << 20)
err := b.Req.ParseMultipartForm(ParseMultipartFormMaxMemory)
if err != nil {
// TODO: all errors caused by client side should be ignored (connection closed).
if !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
Expand Down
39 changes: 34 additions & 5 deletions tests/integration/attachment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ import (
"bytes"
"image"
"image/png"
"io/fs"
"mime/multipart"
"net/http"
"os"
"strings"
"testing"

repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/web"
route_web "code.gitea.io/gitea/routers/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/tests"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func testGeneratePngBytes() []byte {
Expand Down Expand Up @@ -52,14 +58,38 @@ func testCreateIssueAttachment(t *testing.T, session *TestSession, csrf, repoURL
return obj["uuid"]
}

func TestCreateAnonymousAttachment(t *testing.T) {
func TestAttachments(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("CreateAnonymousAttachment", testCreateAnonymousAttachment)
t.Run("CreateUser2IssueAttachment", testCreateUser2IssueAttachment)
t.Run("UploadAttachmentDeleteTemp", testUploadAttachmentDeleteTemp)
t.Run("GetAttachment", testGetAttachment)
}

func testUploadAttachmentDeleteTemp(t *testing.T) {
session := loginUser(t, "user2")
countTmpFile := func() int {
// TODO: GOLANG-HTTP-TMPDIR: Golang saves the uploaded file to os.TempDir() when it exceeds the max memory limit.
files, err := fs.Glob(os.DirFS(os.TempDir()), "multipart-*") //nolint:usetesting // Golang's "http" package's behavior
require.NoError(t, err)
return len(files)
}
var tmpFileCountDuringUpload int
defer test.MockVariableValue(&context.ParseMultipartFormMaxMemory, 1)()
defer web.RouteMock(route_web.RouterMockPointBeforeWebRoutes, func(resp http.ResponseWriter, req *http.Request) {
tmpFileCountDuringUpload = countTmpFile()
})()
_ = testCreateIssueAttachment(t, session, GetUserCSRFToken(t, session), "user2/repo1", "image.png", testGeneratePngBytes(), http.StatusOK)
assert.Equal(t, 1, tmpFileCountDuringUpload, "the temp file should exist when uploaded size exceeds the parse form's max memory")
assert.Equal(t, 0, countTmpFile(), "the temp file should be deleted after upload")
}

func testCreateAnonymousAttachment(t *testing.T) {
session := emptyTestSession(t)
testCreateIssueAttachment(t, session, GetAnonymousCSRFToken(t, session), "user2/repo1", "image.png", testGeneratePngBytes(), http.StatusSeeOther)
}

func TestCreateIssueAttachment(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testCreateUser2IssueAttachment(t *testing.T) {
const repoURL = "user2/repo1"
session := loginUser(t, "user2")
uuid := testCreateIssueAttachment(t, session, GetUserCSRFToken(t, session), repoURL, "image.png", testGeneratePngBytes(), http.StatusOK)
Expand Down Expand Up @@ -90,8 +120,7 @@ func TestCreateIssueAttachment(t *testing.T) {
MakeRequest(t, req, http.StatusOK)
}

func TestGetAttachment(t *testing.T) {
defer tests.PrepareTestEnv(t)()
func testGetAttachment(t *testing.T) {
adminSession := loginUser(t, "user1")
user2Session := loginUser(t, "user2")
user8Session := loginUser(t, "user8")
Expand Down