mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Refactor "route" related code, fix Safari cookie bug (#24330)
Fix #24176 Clean some misuses of route package, clean some legacy FIXMEs --------- Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
		@@ -343,6 +343,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.Commit = commit
 | 
			
		||||
			ctx.Repo.TreePath = ctx.Params("*")
 | 
			
		||||
			next.ServeHTTP(w, req)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -446,6 +446,17 @@ func (ctx *Context) JSON(status int, content interface{}) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func removeSessionCookieHeader(w http.ResponseWriter) {
 | 
			
		||||
	cookies := w.Header()["Set-Cookie"]
 | 
			
		||||
	w.Header().Del("Set-Cookie")
 | 
			
		||||
	for _, cookie := range cookies {
 | 
			
		||||
		if strings.HasPrefix(cookie, setting.SessionConfig.CookieName+"=") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		w.Header().Add("Set-Cookie", cookie)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Redirect redirects the request
 | 
			
		||||
func (ctx *Context) Redirect(location string, status ...int) {
 | 
			
		||||
	code := http.StatusSeeOther
 | 
			
		||||
@@ -453,6 +464,15 @@ func (ctx *Context) Redirect(location string, status ...int) {
 | 
			
		||||
		code = status[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.Contains(location, "://") || strings.HasPrefix(location, "//") {
 | 
			
		||||
		// Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path
 | 
			
		||||
		// 1. the first request to "/my-path" contains cookie
 | 
			
		||||
		// 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking)
 | 
			
		||||
		// 3. Gitea's Sessioner doesn't see the session cookie, so it generates a new session id, and returns it to browser
 | 
			
		||||
		// 4. then the browser accepts the empty session, then the user is logged out
 | 
			
		||||
		// So in this case, we should remove the session cookie from the response header
 | 
			
		||||
		removeSessionCookieHeader(ctx.Resp)
 | 
			
		||||
	}
 | 
			
		||||
	http.Redirect(ctx.Resp, ctx.Req, location, code)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								modules/context/context_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								modules/context/context_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package context
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type mockResponseWriter struct {
 | 
			
		||||
	header http.Header
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mockResponseWriter) Header() http.Header {
 | 
			
		||||
	return m.header
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mockResponseWriter) Write(bytes []byte) (int, error) {
 | 
			
		||||
	panic("implement me")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mockResponseWriter) WriteHeader(statusCode int) {
 | 
			
		||||
	panic("implement me")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRemoveSessionCookieHeader(t *testing.T) {
 | 
			
		||||
	w := &mockResponseWriter{}
 | 
			
		||||
	w.header = http.Header{}
 | 
			
		||||
	w.header.Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "foo"}).String())
 | 
			
		||||
	w.header.Add("Set-Cookie", (&http.Cookie{Name: "other", Value: "bar"}).String())
 | 
			
		||||
	assert.Len(t, w.Header().Values("Set-Cookie"), 2)
 | 
			
		||||
	removeSessionCookieHeader(w)
 | 
			
		||||
	assert.Len(t, w.Header().Values("Set-Cookie"), 1)
 | 
			
		||||
	assert.Contains(t, "other=bar", w.Header().Get("Set-Cookie"))
 | 
			
		||||
}
 | 
			
		||||
@@ -8,7 +8,6 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web/routing"
 | 
			
		||||
@@ -131,16 +130,22 @@ func hasResponseBeenWritten(argsIn []reflect.Value) bool {
 | 
			
		||||
// toHandlerProvider converts a handler to a handler provider
 | 
			
		||||
// A handler provider is a function that takes a "next" http.Handler, it can be used as a middleware
 | 
			
		||||
func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
 | 
			
		||||
	if hp, ok := handler.(func(next http.Handler) http.Handler); ok {
 | 
			
		||||
		return hp
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	funcInfo := routing.GetFuncInfo(handler)
 | 
			
		||||
	fn := reflect.ValueOf(handler)
 | 
			
		||||
	if fn.Type().Kind() != reflect.Func {
 | 
			
		||||
		panic(fmt.Sprintf("handler must be a function, but got %s", fn.Type()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if hp, ok := handler.(func(next http.Handler) http.Handler); ok {
 | 
			
		||||
		return func(next http.Handler) http.Handler {
 | 
			
		||||
			h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info
 | 
			
		||||
			return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | 
			
		||||
				routing.UpdateFuncInfo(req.Context(), funcInfo)
 | 
			
		||||
				h.ServeHTTP(resp, req)
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	provider := func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			// wrap the response writer to check whether the response has been written
 | 
			
		||||
@@ -175,26 +180,3 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
 | 
			
		||||
	provider(nil).ServeHTTP(nil, nil) // do a pre-check to make sure all arguments and return values are supported
 | 
			
		||||
	return provider
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MiddlewareWithPrefix wraps a handler function at a prefix, and make it as a middleware
 | 
			
		||||
// TODO: this design is incorrect, the asset handler should not be a middleware
 | 
			
		||||
func MiddlewareWithPrefix(pathPrefix string, middleware func(handler http.Handler) http.Handler, handlerFunc http.HandlerFunc) func(next http.Handler) http.Handler {
 | 
			
		||||
	funcInfo := routing.GetFuncInfo(handlerFunc)
 | 
			
		||||
	handler := http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		routing.UpdateFuncInfo(req.Context(), funcInfo)
 | 
			
		||||
		handlerFunc(resp, req)
 | 
			
		||||
	})
 | 
			
		||||
	return func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			if !strings.HasPrefix(req.URL.Path, pathPrefix) {
 | 
			
		||||
				next.ServeHTTP(resp, req)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if middleware != nil {
 | 
			
		||||
				middleware(handler).ServeHTTP(resp, req)
 | 
			
		||||
			} else {
 | 
			
		||||
				handler.ServeHTTP(resp, req)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,23 +44,13 @@ type Route struct {
 | 
			
		||||
// NewRoute creates a new route
 | 
			
		||||
func NewRoute() *Route {
 | 
			
		||||
	r := chi.NewRouter()
 | 
			
		||||
	return &Route{
 | 
			
		||||
		R:              r,
 | 
			
		||||
		curGroupPrefix: "",
 | 
			
		||||
		curMiddlewares: []interface{}{},
 | 
			
		||||
	}
 | 
			
		||||
	return &Route{R: r}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Use supports two middlewares
 | 
			
		||||
func (r *Route) Use(middlewares ...interface{}) {
 | 
			
		||||
	if r.curGroupPrefix != "" {
 | 
			
		||||
		// FIXME: this behavior is incorrect, should use "With" instead
 | 
			
		||||
		r.curMiddlewares = append(r.curMiddlewares, middlewares...)
 | 
			
		||||
	} else {
 | 
			
		||||
		// FIXME: another misuse, the "Use" with empty middlewares is called after "Mount"
 | 
			
		||||
		for _, m := range middlewares {
 | 
			
		||||
			r.R.Use(toHandlerProvider(m))
 | 
			
		||||
		}
 | 
			
		||||
	for _, m := range middlewares {
 | 
			
		||||
		r.R.Use(toHandlerProvider(m))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -116,9 +106,7 @@ func (r *Route) Methods(method, pattern string, h []any) {
 | 
			
		||||
 | 
			
		||||
// Mount attaches another Route along ./pattern/*
 | 
			
		||||
func (r *Route) Mount(pattern string, subR *Route) {
 | 
			
		||||
	middlewares := make([]interface{}, len(r.curMiddlewares))
 | 
			
		||||
	copy(middlewares, r.curMiddlewares)
 | 
			
		||||
	subR.Use(middlewares...)
 | 
			
		||||
	subR.Use(r.curMiddlewares...)
 | 
			
		||||
	r.R.Mount(r.getPattern(pattern), subR.R)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,24 +15,23 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web/routing"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/go-chi/session"
 | 
			
		||||
	"github.com/chi-middleware/proxy"
 | 
			
		||||
	chi "github.com/go-chi/chi/v5"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Middlewares returns common middlewares
 | 
			
		||||
func Middlewares() []func(http.Handler) http.Handler {
 | 
			
		||||
	handlers := []func(http.Handler) http.Handler{
 | 
			
		||||
		func(next http.Handler) http.Handler {
 | 
			
		||||
			return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | 
			
		||||
				// First of all escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
 | 
			
		||||
				req.URL.RawPath = req.URL.EscapedPath()
 | 
			
		||||
// ProtocolMiddlewares returns HTTP protocol related middlewares
 | 
			
		||||
func ProtocolMiddlewares() (handlers []any) {
 | 
			
		||||
	handlers = append(handlers, func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			// First of all escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
 | 
			
		||||
			req.URL.RawPath = req.URL.EscapedPath()
 | 
			
		||||
 | 
			
		||||
				ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true)
 | 
			
		||||
				defer finished()
 | 
			
		||||
				next.ServeHTTP(context.NewResponse(resp), req.WithContext(cache.WithCacheContext(ctx)))
 | 
			
		||||
			})
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
			ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true)
 | 
			
		||||
			defer finished()
 | 
			
		||||
			next.ServeHTTP(context.NewResponse(resp), req.WithContext(cache.WithCacheContext(ctx)))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if setting.ReverseProxyLimit > 0 {
 | 
			
		||||
		opt := proxy.NewForwardedHeadersOptions().
 | 
			
		||||
@@ -112,3 +111,17 @@ func stripSlashesMiddleware(next http.Handler) http.Handler {
 | 
			
		||||
		next.ServeHTTP(resp, req)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Sessioner() func(next http.Handler) http.Handler {
 | 
			
		||||
	return session.Sessioner(session.Options{
 | 
			
		||||
		Provider:       setting.SessionConfig.Provider,
 | 
			
		||||
		ProviderConfig: setting.SessionConfig.ProviderConfig,
 | 
			
		||||
		CookieName:     setting.SessionConfig.CookieName,
 | 
			
		||||
		CookiePath:     setting.SessionConfig.CookiePath,
 | 
			
		||||
		Gclifetime:     setting.SessionConfig.Gclifetime,
 | 
			
		||||
		Maxlifetime:    setting.SessionConfig.Maxlifetime,
 | 
			
		||||
		Secure:         setting.SessionConfig.Secure,
 | 
			
		||||
		SameSite:       setting.SessionConfig.SameSite,
 | 
			
		||||
		Domain:         setting.SessionConfig.Domain,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -177,20 +177,15 @@ func GlobalInitInstalled(ctx context.Context) {
 | 
			
		||||
func NormalRoutes(ctx context.Context) *web.Route {
 | 
			
		||||
	ctx, _ = templates.HTMLRenderer(ctx)
 | 
			
		||||
	r := web.NewRoute()
 | 
			
		||||
	for _, middle := range common.Middlewares() {
 | 
			
		||||
		r.Use(middle)
 | 
			
		||||
	}
 | 
			
		||||
	r.Use(common.ProtocolMiddlewares()...)
 | 
			
		||||
 | 
			
		||||
	r.Mount("/", web_routers.Routes(ctx))
 | 
			
		||||
	r.Mount("/api/v1", apiv1.Routes(ctx))
 | 
			
		||||
	r.Mount("/api/internal", private.Routes())
 | 
			
		||||
 | 
			
		||||
	if setting.Packages.Enabled {
 | 
			
		||||
		// Add endpoints to match common package manager APIs
 | 
			
		||||
 | 
			
		||||
		// This implements package support for most package managers
 | 
			
		||||
		r.Mount("/api/packages", packages_router.CommonRoutes(ctx))
 | 
			
		||||
 | 
			
		||||
		// This implements the OCI API (Note this is not preceded by /api but is instead /v2)
 | 
			
		||||
		r.Mount("/v2", packages_router.ContainerRoutes(ctx))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,8 +19,6 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/routers/common"
 | 
			
		||||
	"code.gitea.io/gitea/routers/web/healthcheck"
 | 
			
		||||
	"code.gitea.io/gitea/services/forms"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/go-chi/session"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type dataStore map[string]interface{}
 | 
			
		||||
@@ -30,7 +28,6 @@ func (d *dataStore) GetData() map[string]interface{} {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler {
 | 
			
		||||
	_, rnd := templates.HTMLRenderer(ctx)
 | 
			
		||||
	return func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			defer func() {
 | 
			
		||||
@@ -69,6 +66,7 @@ func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler {
 | 
			
		||||
					if !setting.IsProd {
 | 
			
		||||
						store["ErrorMsg"] = combinedErr
 | 
			
		||||
					}
 | 
			
		||||
					_, rnd := templates.HTMLRenderer(ctx)
 | 
			
		||||
					err = rnd.HTML(w, http.StatusInternalServerError, "status/500", templates.BaseVars().Merge(store))
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						log.Error("%v", err)
 | 
			
		||||
@@ -83,34 +81,22 @@ func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler {
 | 
			
		||||
 | 
			
		||||
// Routes registers the installation routes
 | 
			
		||||
func Routes(ctx goctx.Context) *web.Route {
 | 
			
		||||
	base := web.NewRoute()
 | 
			
		||||
	base.Use(common.ProtocolMiddlewares()...)
 | 
			
		||||
	base.RouteMethods("/assets/*", "GET, HEAD", public.AssetsHandlerFunc("/assets/"))
 | 
			
		||||
 | 
			
		||||
	r := web.NewRoute()
 | 
			
		||||
	for _, middle := range common.Middlewares() {
 | 
			
		||||
		r.Use(middle)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.Use(web.MiddlewareWithPrefix("/assets/", nil, public.AssetsHandlerFunc("/assets/")))
 | 
			
		||||
 | 
			
		||||
	r.Use(session.Sessioner(session.Options{
 | 
			
		||||
		Provider:       setting.SessionConfig.Provider,
 | 
			
		||||
		ProviderConfig: setting.SessionConfig.ProviderConfig,
 | 
			
		||||
		CookieName:     setting.SessionConfig.CookieName,
 | 
			
		||||
		CookiePath:     setting.SessionConfig.CookiePath,
 | 
			
		||||
		Gclifetime:     setting.SessionConfig.Gclifetime,
 | 
			
		||||
		Maxlifetime:    setting.SessionConfig.Maxlifetime,
 | 
			
		||||
		Secure:         setting.SessionConfig.Secure,
 | 
			
		||||
		SameSite:       setting.SessionConfig.SameSite,
 | 
			
		||||
		Domain:         setting.SessionConfig.Domain,
 | 
			
		||||
	}))
 | 
			
		||||
 | 
			
		||||
	r.Use(common.Sessioner())
 | 
			
		||||
	r.Use(installRecovery(ctx))
 | 
			
		||||
	r.Use(Init(ctx))
 | 
			
		||||
	r.Get("/", Install) // it must be on the root, because the "install.js" use the window.location to replace the "localhost" AppURL
 | 
			
		||||
	r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall)
 | 
			
		||||
	r.Get("/post-install", InstallDone)
 | 
			
		||||
	r.Get("/api/healthz", healthcheck.Check)
 | 
			
		||||
 | 
			
		||||
	r.NotFound(installNotFound)
 | 
			
		||||
	return r
 | 
			
		||||
 | 
			
		||||
	base.Mount("", r)
 | 
			
		||||
	return base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func installNotFound(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,11 +11,14 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestRoutes(t *testing.T) {
 | 
			
		||||
	// TODO: this test seems not really testing the handlers
 | 
			
		||||
	ctx, cancel := context.WithCancel(context.Background())
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	routes := Routes(ctx)
 | 
			
		||||
	assert.NotNil(t, routes)
 | 
			
		||||
	assert.EqualValues(t, "/", routes.R.Routes()[0].Pattern)
 | 
			
		||||
	assert.Nil(t, routes.R.Routes()[0].SubRoutes)
 | 
			
		||||
	assert.Len(t, routes.R.Routes()[0].Handlers, 2)
 | 
			
		||||
	base := Routes(ctx)
 | 
			
		||||
	assert.NotNil(t, base)
 | 
			
		||||
	r := base.R.Routes()[1]
 | 
			
		||||
	routes := r.SubRoutes.Routes()[0]
 | 
			
		||||
	assert.EqualValues(t, "/", routes.Pattern)
 | 
			
		||||
	assert.Nil(t, routes.SubRoutes)
 | 
			
		||||
	assert.Len(t, routes.Handlers, 2)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,12 +59,7 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				http.Redirect(
 | 
			
		||||
					w,
 | 
			
		||||
					req,
 | 
			
		||||
					u.String(),
 | 
			
		||||
					http.StatusTemporaryRedirect,
 | 
			
		||||
				)
 | 
			
		||||
				http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect)
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -122,9 +117,9 @@ func (d *dataStore) GetData() map[string]interface{} {
 | 
			
		||||
	return *d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so.
 | 
			
		||||
// RecoveryWith500Page returns a middleware that recovers from any panics and writes a 500 and a log if so.
 | 
			
		||||
// This error will be created with the gitea 500 page.
 | 
			
		||||
func Recovery(ctx goctx.Context) func(next http.Handler) http.Handler {
 | 
			
		||||
func RecoveryWith500Page(ctx goctx.Context) func(next http.Handler) http.Handler {
 | 
			
		||||
	_, rnd := templates.HTMLRenderer(ctx)
 | 
			
		||||
	return func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										49
									
								
								routers/web/misc/misc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								routers/web/misc/misc.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package misc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/httpcache"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func SSHInfo(rw http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	if !git.SupportProcReceive {
 | 
			
		||||
		rw.WriteHeader(http.StatusNotFound)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	rw.Header().Set("content-type", "text/json;charset=UTF-8")
 | 
			
		||||
	_, err := rw.Write([]byte(`{"type":"gitea","version":1}`))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("fail to write result: err: %v", err)
 | 
			
		||||
		rw.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	rw.WriteHeader(http.StatusOK)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DummyOK(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	w.WriteHeader(http.StatusOK)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RobotsTxt(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	filePath := path.Join(setting.CustomPath, "robots.txt")
 | 
			
		||||
	fi, err := os.Stat(filePath)
 | 
			
		||||
	if err == nil && httpcache.HandleTimeCache(req, w, fi) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	http.ServeFile(w, req, filePath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func StaticRedirect(target string) func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	return func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		http.Redirect(w, req, path.Join(setting.StaticURLPrefix, target), http.StatusMovedPermanently)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -18,7 +18,9 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/unit"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/cache"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
@@ -59,6 +61,22 @@ func MustBeAbleToUpload(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CommitInfoCache(ctx *context.Context) {
 | 
			
		||||
	var err error
 | 
			
		||||
	ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetBranchCommit", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetCommitsCount", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
 | 
			
		||||
	ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkContextUser(ctx *context.Context, uid int64) *user_model.User {
 | 
			
		||||
	orgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx.Doer.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,8 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup/markdown"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"code.gitea.io/gitea/routers/web/feed"
 | 
			
		||||
	context_service "code.gitea.io/gitea/services/context"
 | 
			
		||||
	issue_service "code.gitea.io/gitea/services/issue"
 | 
			
		||||
	pull_service "code.gitea.io/gitea/services/pull"
 | 
			
		||||
 | 
			
		||||
@@ -815,3 +817,51 @@ func ShowGPGKeys(ctx *context.Context) {
 | 
			
		||||
	writer.Close()
 | 
			
		||||
	ctx.PlainTextBytes(http.StatusOK, buf.Bytes())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UsernameSubRoute(ctx *context.Context) {
 | 
			
		||||
	// WORKAROUND to support usernames with "." in it
 | 
			
		||||
	// https://github.com/go-chi/chi/issues/781
 | 
			
		||||
	username := ctx.Params("username")
 | 
			
		||||
	reloadParam := func(suffix string) (success bool) {
 | 
			
		||||
		ctx.SetParams("username", strings.TrimSuffix(username, suffix))
 | 
			
		||||
		context_service.UserAssignmentWeb()(ctx)
 | 
			
		||||
		return !ctx.Written()
 | 
			
		||||
	}
 | 
			
		||||
	switch {
 | 
			
		||||
	case strings.HasSuffix(username, ".png"):
 | 
			
		||||
		if reloadParam(".png") {
 | 
			
		||||
			AvatarByUserName(ctx)
 | 
			
		||||
		}
 | 
			
		||||
	case strings.HasSuffix(username, ".keys"):
 | 
			
		||||
		if reloadParam(".keys") {
 | 
			
		||||
			ShowSSHKeys(ctx)
 | 
			
		||||
		}
 | 
			
		||||
	case strings.HasSuffix(username, ".gpg"):
 | 
			
		||||
		if reloadParam(".gpg") {
 | 
			
		||||
			ShowGPGKeys(ctx)
 | 
			
		||||
		}
 | 
			
		||||
	case strings.HasSuffix(username, ".rss"):
 | 
			
		||||
		if !setting.Other.EnableFeed {
 | 
			
		||||
			ctx.Error(http.StatusNotFound)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if reloadParam(".rss") {
 | 
			
		||||
			context_service.UserAssignmentWeb()(ctx)
 | 
			
		||||
			feed.ShowUserFeedRSS(ctx)
 | 
			
		||||
		}
 | 
			
		||||
	case strings.HasSuffix(username, ".atom"):
 | 
			
		||||
		if !setting.Other.EnableFeed {
 | 
			
		||||
			ctx.Error(http.StatusNotFound)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if reloadParam(".atom") {
 | 
			
		||||
			feed.ShowUserFeedAtom(ctx)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		context_service.UserAssignmentWeb()(ctx)
 | 
			
		||||
		if !ctx.Written() {
 | 
			
		||||
			ctx.Data["EnableFeed"] = setting.Other.EnableFeed
 | 
			
		||||
			Profile(ctx)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,16 +6,10 @@ package web
 | 
			
		||||
import (
 | 
			
		||||
	gocontext "context"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/perm"
 | 
			
		||||
	"code.gitea.io/gitea/models/unit"
 | 
			
		||||
	"code.gitea.io/gitea/modules/cache"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/httpcache"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/metrics"
 | 
			
		||||
	"code.gitea.io/gitea/modules/public"
 | 
			
		||||
@@ -26,6 +20,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/validation"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web/routing"
 | 
			
		||||
	"code.gitea.io/gitea/routers/common"
 | 
			
		||||
	"code.gitea.io/gitea/routers/web/admin"
 | 
			
		||||
	"code.gitea.io/gitea/routers/web/auth"
 | 
			
		||||
	"code.gitea.io/gitea/routers/web/devtest"
 | 
			
		||||
@@ -48,7 +43,6 @@ import (
 | 
			
		||||
	_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
 | 
			
		||||
 | 
			
		||||
	"gitea.com/go-chi/captcha"
 | 
			
		||||
	"gitea.com/go-chi/session"
 | 
			
		||||
	"github.com/NYTimes/gziphandler"
 | 
			
		||||
	"github.com/go-chi/chi/v5/middleware"
 | 
			
		||||
	"github.com/go-chi/cors"
 | 
			
		||||
@@ -103,45 +97,18 @@ func buildAuthGroup() *auth_service.Group {
 | 
			
		||||
func Routes(ctx gocontext.Context) *web.Route {
 | 
			
		||||
	routes := web.NewRoute()
 | 
			
		||||
 | 
			
		||||
	routes.Use(web.MiddlewareWithPrefix("/assets/", CorsHandler(), public.AssetsHandlerFunc("/assets/")))
 | 
			
		||||
 | 
			
		||||
	sessioner := session.Sessioner(session.Options{
 | 
			
		||||
		Provider:       setting.SessionConfig.Provider,
 | 
			
		||||
		ProviderConfig: setting.SessionConfig.ProviderConfig,
 | 
			
		||||
		CookieName:     setting.SessionConfig.CookieName,
 | 
			
		||||
		CookiePath:     setting.SessionConfig.CookiePath,
 | 
			
		||||
		Gclifetime:     setting.SessionConfig.Gclifetime,
 | 
			
		||||
		Maxlifetime:    setting.SessionConfig.Maxlifetime,
 | 
			
		||||
		Secure:         setting.SessionConfig.Secure,
 | 
			
		||||
		SameSite:       setting.SessionConfig.SameSite,
 | 
			
		||||
		Domain:         setting.SessionConfig.Domain,
 | 
			
		||||
	})
 | 
			
		||||
	routes.Use(sessioner)
 | 
			
		||||
 | 
			
		||||
	ctx, _ = templates.HTMLRenderer(ctx)
 | 
			
		||||
 | 
			
		||||
	routes.Use(Recovery(ctx))
 | 
			
		||||
 | 
			
		||||
	// We use r.Route here over r.Use because this prevents requests that are not for avatars having to go through this additional handler
 | 
			
		||||
	routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler
 | 
			
		||||
	routes.RouteMethods("/assets/*", "GET, HEAD", CorsHandler(), public.AssetsHandlerFunc("/assets/"))
 | 
			
		||||
	routes.RouteMethods("/avatars/*", "GET, HEAD", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
 | 
			
		||||
	routes.RouteMethods("/repo-avatars/*", "GET, HEAD", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
 | 
			
		||||
	routes.RouteMethods("/apple-touch-icon.png", "GET, HEAD", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
 | 
			
		||||
	routes.RouteMethods("/favicon.ico", "GET, HEAD", misc.StaticRedirect("/assets/img/favicon.png"))
 | 
			
		||||
 | 
			
		||||
	// for health check - doesn't need to be passed through gzip handler
 | 
			
		||||
	routes.Head("/", func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		w.WriteHeader(http.StatusOK)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// this png is very likely to always be below the limit for gzip so it doesn't need to pass through gzip
 | 
			
		||||
	routes.Get("/apple-touch-icon.png", func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "/assets/img/apple-touch-icon.png"), http.StatusPermanentRedirect)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// redirect default favicon to the path of the custom favicon with a default as a fallback
 | 
			
		||||
	routes.Get("/favicon.ico", func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "/assets/img/favicon.png"), http.StatusMovedPermanently)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	common := []interface{}{}
 | 
			
		||||
	ctx, _ = templates.HTMLRenderer(ctx)
 | 
			
		||||
	common := []any{
 | 
			
		||||
		common.Sessioner(),
 | 
			
		||||
		RecoveryWith500Page(ctx),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setting.EnableGzip {
 | 
			
		||||
		h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize))
 | 
			
		||||
@@ -157,42 +124,18 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setting.HasRobotsTxt {
 | 
			
		||||
		routes.Get("/robots.txt", append(common, func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			filePath := path.Join(setting.CustomPath, "robots.txt")
 | 
			
		||||
			fi, err := os.Stat(filePath)
 | 
			
		||||
			if err == nil && httpcache.HandleTimeCache(req, w, fi) {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			http.ServeFile(w, req, filePath)
 | 
			
		||||
		})...)
 | 
			
		||||
		routes.Get("/robots.txt", append(common, misc.RobotsTxt)...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// prometheus metrics endpoint - do not need to go through contexter
 | 
			
		||||
	if setting.Metrics.Enabled {
 | 
			
		||||
		c := metrics.NewCollector()
 | 
			
		||||
		prometheus.MustRegister(c)
 | 
			
		||||
 | 
			
		||||
		prometheus.MustRegister(metrics.NewCollector())
 | 
			
		||||
		routes.Get("/metrics", append(common, Metrics)...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	routes.Get("/ssh_info", func(rw http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		if !git.SupportProcReceive {
 | 
			
		||||
			rw.WriteHeader(http.StatusNotFound)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		rw.Header().Set("content-type", "text/json;charset=UTF-8")
 | 
			
		||||
		_, err := rw.Write([]byte(`{"type":"gitea","version":1}`))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("fail to write result: err: %v", err)
 | 
			
		||||
			rw.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		rw.WriteHeader(http.StatusOK)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	routes.Get("/ssh_info", misc.SSHInfo)
 | 
			
		||||
	routes.Get("/api/healthz", healthcheck.Check)
 | 
			
		||||
 | 
			
		||||
	// Removed: toolbox.Toolboxer middleware will provide debug information which seems unnecessary
 | 
			
		||||
	common = append(common, context.Contexter(ctx))
 | 
			
		||||
 | 
			
		||||
	group := buildAuthGroup()
 | 
			
		||||
@@ -207,7 +150,7 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
			
		||||
	common = append(common, middleware.GetHead)
 | 
			
		||||
 | 
			
		||||
	if setting.API.EnableSwagger {
 | 
			
		||||
		// Note: The route moved from apiroutes because it's in fact want to render a web page
 | 
			
		||||
		// Note: The route is here but no in API routes because it renders a web page
 | 
			
		||||
		routes.Get("/api/swagger", append(common, misc.Swagger)...) // Render V1 by default
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -217,17 +160,14 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
			
		||||
	common = append(common, goGet)
 | 
			
		||||
 | 
			
		||||
	others := web.NewRoute()
 | 
			
		||||
	for _, middle := range common {
 | 
			
		||||
		others.Use(middle)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	RegisterRoutes(others)
 | 
			
		||||
	others.Use(common...)
 | 
			
		||||
	registerRoutes(others)
 | 
			
		||||
	routes.Mount("", others)
 | 
			
		||||
	return routes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegisterRoutes register routes
 | 
			
		||||
func RegisterRoutes(m *web.Route) {
 | 
			
		||||
// registerRoutes register routes
 | 
			
		||||
func registerRoutes(m *web.Route) {
 | 
			
		||||
	reqSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: true})
 | 
			
		||||
	ignSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: setting.Service.RequireSignInView})
 | 
			
		||||
	ignExploreSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView})
 | 
			
		||||
@@ -354,8 +294,8 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
			m.Get("/nodeinfo", NodeInfoLinks)
 | 
			
		||||
			m.Get("/webfinger", WebfingerQuery)
 | 
			
		||||
		}, federationEnabled)
 | 
			
		||||
		m.Get("/change-password", func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			http.Redirect(w, req, "/user/settings/account", http.StatusTemporaryRedirect)
 | 
			
		||||
		m.Get("/change-password", func(ctx *context.Context) {
 | 
			
		||||
			ctx.Redirect(setting.AppSubURL + "/user/settings/account")
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
@@ -664,53 +604,7 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
	// ***** END: Admin *****
 | 
			
		||||
 | 
			
		||||
	m.Group("", func() {
 | 
			
		||||
		m.Get("/favicon.ico", func(ctx *context.Context) {
 | 
			
		||||
			ctx.SetServeHeaders(&context.ServeHeaderOptions{
 | 
			
		||||
				Filename: "favicon.png",
 | 
			
		||||
			})
 | 
			
		||||
			http.ServeFile(ctx.Resp, ctx.Req, path.Join(setting.StaticRootPath, "public/img/favicon.png"))
 | 
			
		||||
		})
 | 
			
		||||
		m.Get("/{username}", func(ctx *context.Context) {
 | 
			
		||||
			// WORKAROUND to support usernames with "." in it
 | 
			
		||||
			// https://github.com/go-chi/chi/issues/781
 | 
			
		||||
			username := ctx.Params("username")
 | 
			
		||||
			reloadParam := func(suffix string) (success bool) {
 | 
			
		||||
				ctx.SetParams("username", strings.TrimSuffix(username, suffix))
 | 
			
		||||
				context_service.UserAssignmentWeb()(ctx)
 | 
			
		||||
				return !ctx.Written()
 | 
			
		||||
			}
 | 
			
		||||
			switch {
 | 
			
		||||
			case strings.HasSuffix(username, ".png"):
 | 
			
		||||
				if reloadParam(".png") {
 | 
			
		||||
					user.AvatarByUserName(ctx)
 | 
			
		||||
				}
 | 
			
		||||
			case strings.HasSuffix(username, ".keys"):
 | 
			
		||||
				if reloadParam(".keys") {
 | 
			
		||||
					user.ShowSSHKeys(ctx)
 | 
			
		||||
				}
 | 
			
		||||
			case strings.HasSuffix(username, ".gpg"):
 | 
			
		||||
				if reloadParam(".gpg") {
 | 
			
		||||
					user.ShowGPGKeys(ctx)
 | 
			
		||||
				}
 | 
			
		||||
			case strings.HasSuffix(username, ".rss"):
 | 
			
		||||
				feedEnabled(ctx)
 | 
			
		||||
				if !ctx.Written() && reloadParam(".rss") {
 | 
			
		||||
					context_service.UserAssignmentWeb()(ctx)
 | 
			
		||||
					feed.ShowUserFeedRSS(ctx)
 | 
			
		||||
				}
 | 
			
		||||
			case strings.HasSuffix(username, ".atom"):
 | 
			
		||||
				feedEnabled(ctx)
 | 
			
		||||
				if !ctx.Written() && reloadParam(".atom") {
 | 
			
		||||
					feed.ShowUserFeedAtom(ctx)
 | 
			
		||||
				}
 | 
			
		||||
			default:
 | 
			
		||||
				context_service.UserAssignmentWeb()(ctx)
 | 
			
		||||
				if !ctx.Written() {
 | 
			
		||||
					ctx.Data["EnableFeed"] = setting.Other.EnableFeed
 | 
			
		||||
					user.Profile(ctx)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
		m.Get("/{username}", user.UsernameSubRoute)
 | 
			
		||||
		m.Get("/attachments/{uuid}", repo.GetAttachment)
 | 
			
		||||
	}, ignSignIn)
 | 
			
		||||
 | 
			
		||||
@@ -1233,21 +1127,7 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
		m.Group("/releases", func() {
 | 
			
		||||
			m.Get("/edit/*", repo.EditRelease)
 | 
			
		||||
			m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost)
 | 
			
		||||
		}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, func(ctx *context.Context) {
 | 
			
		||||
			var err error
 | 
			
		||||
			ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetBranchCommit", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("GetCommitsCount", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
 | 
			
		||||
			ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
 | 
			
		||||
		})
 | 
			
		||||
		}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, repo.CommitInfoCache)
 | 
			
		||||
	}, ignSignIn, context.RepoAssignment, context.UnitTypes(), reqRepoReleaseReader)
 | 
			
		||||
 | 
			
		||||
	// to maintain compatibility with old attachments
 | 
			
		||||
@@ -1326,18 +1206,10 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
		m.Group("/wiki", func() {
 | 
			
		||||
			m.Combo("/").
 | 
			
		||||
				Get(repo.Wiki).
 | 
			
		||||
				Post(context.RepoMustNotBeArchived(),
 | 
			
		||||
					reqSignIn,
 | 
			
		||||
					reqRepoWikiWriter,
 | 
			
		||||
					web.Bind(forms.NewWikiForm{}),
 | 
			
		||||
					repo.WikiPost)
 | 
			
		||||
				Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost)
 | 
			
		||||
			m.Combo("/*").
 | 
			
		||||
				Get(repo.Wiki).
 | 
			
		||||
				Post(context.RepoMustNotBeArchived(),
 | 
			
		||||
					reqSignIn,
 | 
			
		||||
					reqRepoWikiWriter,
 | 
			
		||||
					web.Bind(forms.NewWikiForm{}),
 | 
			
		||||
					repo.WikiPost)
 | 
			
		||||
				Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost)
 | 
			
		||||
			m.Get("/commit/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
 | 
			
		||||
			m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff)
 | 
			
		||||
		}, repo.MustEnableWiki, func(ctx *context.Context) {
 | 
			
		||||
@@ -1468,8 +1340,7 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
		m.Group("", func() {
 | 
			
		||||
			m.Get("/forks", repo.Forks)
 | 
			
		||||
		}, context.RepoRef(), reqRepoCodeReader)
 | 
			
		||||
		m.Get("/commit/{sha:([a-f0-9]{7,40})}.{ext:patch|diff}",
 | 
			
		||||
			repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
 | 
			
		||||
		m.Get("/commit/{sha:([a-f0-9]{7,40})}.{ext:patch|diff}", repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
 | 
			
		||||
	}, ignSignIn, context.RepoAssignment, context.UnitTypes())
 | 
			
		||||
 | 
			
		||||
	m.Post("/{username}/{reponame}/lastcommit/*", ignSignInAndCsrf, context.RepoAssignment, context.UnitTypes(), context.RepoRefByType(context.RepoRefCommit), reqRepoCodeReader, repo.LastCommit)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user