mirror of
https://github.com/go-gitea/gitea.git
synced 2025-12-13 18:32:54 +00:00
Compare commits
3 Commits
e98d9bb93e
...
432e128074
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
432e128074 | ||
|
|
8d6442a43e | ||
|
|
3d66e75a47 |
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
{{if not .IsDisplayingSource}}data-raw-file-link="{{$.RawFileLink}}"{{end}}
|
||||
data-tooltip-content="{{if .CanCopyContent}}{{ctx.Locale.Tr "copy_content"}}{{else}}{{ctx.Locale.Tr "copy_type_unsupported"}}{{end}}"
|
||||
>{{svg "octicon-copy"}}</a>
|
||||
{{if .EnableFeed}}
|
||||
{{if and .EnableFeed .RefFullName.IsBranch}}
|
||||
<a class="btn-octicon" href="{{$.RepoLink}}/rss/{{$.RefTypeNameSubURL}}/{{PathEscapeSegments .TreePath}}" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">
|
||||
{{svg "octicon-rss"}}
|
||||
</a>
|
||||
|
||||
@@ -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 {
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
|
||||
--gap-inline: 0.25rem; /* gap for inline texts and elements, for example: the spaces for sentence with labels, button text, etc */
|
||||
--gap-block: 0.5rem; /* gap for element blocks, for example: spaces between buttons, menu image & title, header icon & title etc */
|
||||
|
||||
--background-view-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAG0lEQVQYlWN4+vTpf3SMDTAMBYXYBLFpHgoKAeiOf0SGE9kbAAAAAElFTkSuQmCC") right bottom var(--color-primary-light-7);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1200px) {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
.image-diff-container img {
|
||||
border: 1px solid var(--color-primary-light-7);
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAG0lEQVQYlWN4+vTpf3SMDTAMBYXYBLFpHgoKAeiOf0SGE9kbAAAAAElFTkSuQmCC") right bottom var(--color-primary-light-7);
|
||||
background: var(--background-view-image);
|
||||
}
|
||||
|
||||
.image-diff-container .before-container {
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
.view-raw img[src$=".svg" i] {
|
||||
max-height: 600px !important;
|
||||
max-width: 600px !important;
|
||||
background: var(--background-view-image);
|
||||
}
|
||||
|
||||
.file-view-render-container {
|
||||
|
||||
@@ -38,14 +38,14 @@ function getDefaultSvgBoundsIfUndefined(text: string, src: string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function createContext(imageAfter: HTMLImageElement, imageBefore: HTMLImageElement) {
|
||||
function createContext(imageAfter: HTMLImageElement, imageBefore: HTMLImageElement, svgBoundsInfo: any) {
|
||||
const sizeAfter = {
|
||||
width: imageAfter?.width || 0,
|
||||
height: imageAfter?.height || 0,
|
||||
width: svgBoundsInfo.after?.width || imageAfter?.width || 0,
|
||||
height: svgBoundsInfo.after?.height || imageAfter?.height || 0,
|
||||
};
|
||||
const sizeBefore = {
|
||||
width: imageBefore?.width || 0,
|
||||
height: imageBefore?.height || 0,
|
||||
width: svgBoundsInfo.before?.width || imageBefore?.width || 0,
|
||||
height: svgBoundsInfo.before?.height || imageBefore?.height || 0,
|
||||
};
|
||||
const maxSize = {
|
||||
width: Math.max(sizeBefore.width, sizeAfter.width),
|
||||
@@ -92,7 +92,8 @@ class ImageDiff {
|
||||
boundsInfo: containerEl.querySelector('.bounds-info-before'),
|
||||
}];
|
||||
|
||||
await Promise.all(imageInfos.map(async (info) => {
|
||||
const svgBoundsInfo: any = {before: null, after: null};
|
||||
await Promise.all(imageInfos.map(async (info, index) => {
|
||||
const [success] = await Promise.all(Array.from(info.images, (img) => {
|
||||
return loadElem(img, info.path);
|
||||
}));
|
||||
@@ -102,11 +103,8 @@ class ImageDiff {
|
||||
const resp = await GET(info.path);
|
||||
const text = await resp.text();
|
||||
const bounds = getDefaultSvgBoundsIfUndefined(text, info.path);
|
||||
svgBoundsInfo[index === 0 ? 'after' : 'before'] = bounds;
|
||||
if (bounds) {
|
||||
for (const el of info.images) {
|
||||
el.setAttribute('width', String(bounds.width));
|
||||
el.setAttribute('height', String(bounds.height));
|
||||
}
|
||||
hideElem(info.boundsInfo);
|
||||
}
|
||||
}
|
||||
@@ -115,10 +113,10 @@ class ImageDiff {
|
||||
const imagesAfter = imageInfos[0].images;
|
||||
const imagesBefore = imageInfos[1].images;
|
||||
|
||||
this.initSideBySide(createContext(imagesAfter[0], imagesBefore[0]));
|
||||
this.initSideBySide(createContext(imagesAfter[0], imagesBefore[0], svgBoundsInfo));
|
||||
if (imagesAfter.length > 0 && imagesBefore.length > 0) {
|
||||
this.initSwipe(createContext(imagesAfter[1], imagesBefore[1]));
|
||||
this.initOverlay(createContext(imagesAfter[2], imagesBefore[2]));
|
||||
this.initSwipe(createContext(imagesAfter[1], imagesBefore[1], svgBoundsInfo));
|
||||
this.initOverlay(createContext(imagesAfter[2], imagesBefore[2], svgBoundsInfo));
|
||||
}
|
||||
queryElemChildren(containerEl, '.image-diff-tabs', (el) => el.classList.remove('is-loading'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user