Move macaron to chi (#14293)
Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
parent
3adbbb4255
commit
6433ba0ec3
353 changed files with 5463 additions and 20785 deletions
|
@ -8,19 +8,15 @@ import (
|
|||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
"code.gitea.io/gitea/modules/session"
|
||||
)
|
||||
|
||||
// DataStore represents a data store
|
||||
type DataStore interface {
|
||||
GetData() map[string]interface{}
|
||||
}
|
||||
type DataStore middlewares.DataStore
|
||||
|
||||
// SessionStore represents a session store
|
||||
type SessionStore interface {
|
||||
Get(interface{}) interface{}
|
||||
Set(interface{}, interface{}) error
|
||||
Delete(interface{}) error
|
||||
}
|
||||
type SessionStore session.Store
|
||||
|
||||
// SingleSignOn represents a SSO authentication method (plugin) for HTTP requests.
|
||||
type SingleSignOn interface {
|
||||
|
|
|
@ -62,6 +62,8 @@ func (o *OAuth2) Free() error {
|
|||
|
||||
// userIDFromToken returns the user id corresponding to the OAuth token.
|
||||
func (o *OAuth2) userIDFromToken(req *http.Request, store DataStore) int64 {
|
||||
_ = req.ParseForm()
|
||||
|
||||
// Check access token.
|
||||
tokenSHA := req.Form.Get("token")
|
||||
if len(tokenSHA) == 0 {
|
||||
|
|
6
modules/cache/cache.go
vendored
6
modules/cache/cache.go
vendored
|
@ -10,9 +10,9 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
mc "gitea.com/macaron/cache"
|
||||
mc "gitea.com/go-chi/cache"
|
||||
|
||||
_ "gitea.com/macaron/cache/memcache" // memcache plugin for cache
|
||||
_ "gitea.com/go-chi/cache/memcache" // memcache plugin for cache
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -20,7 +20,7 @@ var (
|
|||
)
|
||||
|
||||
func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
|
||||
return mc.NewCacher(cacheConfig.Adapter, mc.Options{
|
||||
return mc.NewCacher(mc.Options{
|
||||
Adapter: cacheConfig.Adapter,
|
||||
AdapterConfig: cacheConfig.Conn,
|
||||
Interval: cacheConfig.Interval,
|
||||
|
|
2
modules/cache/cache_redis.go
vendored
2
modules/cache/cache_redis.go
vendored
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/nosql"
|
||||
|
||||
"gitea.com/macaron/cache"
|
||||
"gitea.com/go-chi/cache"
|
||||
"github.com/go-redis/redis/v7"
|
||||
"github.com/unknwon/com"
|
||||
)
|
||||
|
|
|
@ -6,18 +6,21 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/auth/sso"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"gitea.com/macaron/csrf"
|
||||
"gitea.com/macaron/macaron"
|
||||
"gitea.com/go-chi/session"
|
||||
)
|
||||
|
||||
// APIContext is a specific macaron context for API service
|
||||
|
@ -91,7 +94,7 @@ func (ctx *APIContext) Error(status int, title string, obj interface{}) {
|
|||
if status == http.StatusInternalServerError {
|
||||
log.ErrorWithSkip(1, "%s: %s", title, message)
|
||||
|
||||
if macaron.Env == macaron.PROD && !(ctx.User != nil && ctx.User.IsAdmin) {
|
||||
if setting.IsProd() && !(ctx.User != nil && ctx.User.IsAdmin) {
|
||||
message = ""
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +111,7 @@ func (ctx *APIContext) InternalServerError(err error) {
|
|||
log.ErrorWithSkip(1, "InternalServerError: %v", err)
|
||||
|
||||
var message string
|
||||
if macaron.Env != macaron.PROD || (ctx.User != nil && ctx.User.IsAdmin) {
|
||||
if !setting.IsProd() || (ctx.User != nil && ctx.User.IsAdmin) {
|
||||
message = err.Error()
|
||||
}
|
||||
|
||||
|
@ -118,6 +121,20 @@ func (ctx *APIContext) InternalServerError(err error) {
|
|||
})
|
||||
}
|
||||
|
||||
var (
|
||||
apiContextKey interface{} = "default_api_context"
|
||||
)
|
||||
|
||||
// WithAPIContext set up api context in request
|
||||
func WithAPIContext(req *http.Request, ctx *APIContext) *http.Request {
|
||||
return req.WithContext(context.WithValue(req.Context(), apiContextKey, ctx))
|
||||
}
|
||||
|
||||
// GetAPIContext returns a context for API routes
|
||||
func GetAPIContext(req *http.Request) *APIContext {
|
||||
return req.Context().Value(apiContextKey).(*APIContext)
|
||||
}
|
||||
|
||||
func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
|
||||
page := NewPagination(total, pageSize, curPage, 0)
|
||||
paginater := page.Paginater
|
||||
|
@ -172,7 +189,7 @@ func (ctx *APIContext) RequireCSRF() {
|
|||
headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())
|
||||
formValueToken := ctx.Req.FormValue(ctx.csrf.GetFormName())
|
||||
if len(headerToken) > 0 || len(formValueToken) > 0 {
|
||||
csrf.Validate(ctx.Context.Context, ctx.csrf)
|
||||
Validate(ctx.Context, ctx.csrf)
|
||||
} else {
|
||||
ctx.Context.Error(401, "Missing CSRF token.")
|
||||
}
|
||||
|
@ -201,42 +218,91 @@ func (ctx *APIContext) CheckForOTP() {
|
|||
}
|
||||
|
||||
// APIContexter returns apicontext as macaron middleware
|
||||
func APIContexter() macaron.Handler {
|
||||
return func(c *Context) {
|
||||
ctx := &APIContext{
|
||||
Context: c,
|
||||
}
|
||||
c.Map(ctx)
|
||||
func APIContexter() func(http.Handler) http.Handler {
|
||||
var csrfOpts = getCsrfOpts()
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var locale = middlewares.Locale(w, req)
|
||||
var ctx = APIContext{
|
||||
Context: &Context{
|
||||
Resp: NewResponse(w),
|
||||
Data: map[string]interface{}{},
|
||||
Locale: locale,
|
||||
Session: session.GetSession(req),
|
||||
Repo: &Repository{
|
||||
PullRequest: &PullRequest{},
|
||||
},
|
||||
Org: &Organization{},
|
||||
},
|
||||
Org: &APIOrganization{},
|
||||
}
|
||||
|
||||
ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx)
|
||||
ctx.csrf = Csrfer(csrfOpts, ctx.Context)
|
||||
|
||||
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
|
||||
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
|
||||
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get user from session if logged in.
|
||||
ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req, ctx.Resp, &ctx, ctx.Session)
|
||||
if ctx.User != nil {
|
||||
ctx.IsSigned = true
|
||||
ctx.Data["IsSigned"] = ctx.IsSigned
|
||||
ctx.Data["SignedUser"] = ctx.User
|
||||
ctx.Data["SignedUserID"] = ctx.User.ID
|
||||
ctx.Data["SignedUserName"] = ctx.User.Name
|
||||
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
|
||||
} else {
|
||||
ctx.Data["SignedUserID"] = int64(0)
|
||||
ctx.Data["SignedUserName"] = ""
|
||||
}
|
||||
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
|
||||
|
||||
ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
|
||||
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ReferencesGitRepo injects the GitRepo into the Context
|
||||
func ReferencesGitRepo(allowEmpty bool) macaron.Handler {
|
||||
return func(ctx *APIContext) {
|
||||
// Empty repository does not have reference information.
|
||||
if !allowEmpty && ctx.Repo.Repository.IsEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
// For API calls.
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
ctx.Error(500, "RepoRef Invalid repo "+repoPath, err)
|
||||
func ReferencesGitRepo(allowEmpty bool) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := GetAPIContext(req)
|
||||
// Empty repository does not have reference information.
|
||||
if !allowEmpty && ctx.Repo.Repository.IsEmpty {
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
// We opened it, we should close it
|
||||
defer func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
ctx.Next()
|
||||
// For API calls.
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
ctx.Error(500, "RepoRef Invalid repo "+repoPath, err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
// We opened it, we should close it
|
||||
defer func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,8 +332,9 @@ func (ctx *APIContext) NotFound(objs ...interface{}) {
|
|||
}
|
||||
|
||||
// RepoRefForAPI handles repository reference names when the ref name is not explicitly given
|
||||
func RepoRefForAPI() macaron.Handler {
|
||||
return func(ctx *APIContext) {
|
||||
func RepoRefForAPI(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := GetAPIContext(req)
|
||||
// Empty repository does not have reference information.
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
return
|
||||
|
@ -319,6 +386,6 @@ func RepoRefForAPI() macaron.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
ctx.Next()
|
||||
}
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,12 +7,8 @@ package context
|
|||
|
||||
import (
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/auth"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"gitea.com/macaron/csrf"
|
||||
"gitea.com/macaron/macaron"
|
||||
)
|
||||
|
||||
// ToggleOptions contains required or check options
|
||||
|
@ -24,42 +20,23 @@ type ToggleOptions struct {
|
|||
}
|
||||
|
||||
// Toggle returns toggle options as middleware
|
||||
func Toggle(options *ToggleOptions) macaron.Handler {
|
||||
func Toggle(options *ToggleOptions) func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
isAPIPath := auth.IsAPIPath(ctx.Req.URL.Path)
|
||||
|
||||
// Check prohibit login users.
|
||||
if ctx.IsSigned {
|
||||
if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
|
||||
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
|
||||
if isAPIPath {
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "This account is not activated.",
|
||||
})
|
||||
return
|
||||
}
|
||||
ctx.HTML(200, "user/auth/activate")
|
||||
return
|
||||
} else if !ctx.User.IsActive || ctx.User.ProhibitLogin {
|
||||
}
|
||||
if !ctx.User.IsActive || ctx.User.ProhibitLogin {
|
||||
log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr())
|
||||
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
|
||||
if isAPIPath {
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "This account is prohibited from signing in, please contact your site administrator.",
|
||||
})
|
||||
return
|
||||
}
|
||||
ctx.HTML(200, "user/auth/prohibit_login")
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.User.MustChangePassword {
|
||||
if isAPIPath {
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
|
||||
})
|
||||
return
|
||||
}
|
||||
if ctx.Req.URL.Path != "/user/settings/change_password" {
|
||||
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
|
||||
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
|
||||
|
@ -82,8 +59,8 @@ func Toggle(options *ToggleOptions) macaron.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" && !auth.IsAPIPath(ctx.Req.URL.Path) {
|
||||
csrf.Validate(ctx.Context, ctx.csrf)
|
||||
if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" {
|
||||
Validate(ctx, ctx.csrf)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
@ -91,13 +68,6 @@ func Toggle(options *ToggleOptions) macaron.Handler {
|
|||
|
||||
if options.SignInRequired {
|
||||
if !ctx.IsSigned {
|
||||
// Restrict API calls with error message.
|
||||
if isAPIPath {
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "Only signed in user is allowed to call APIs.",
|
||||
})
|
||||
return
|
||||
}
|
||||
if ctx.Req.URL.Path != "/user/events" {
|
||||
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
|
||||
}
|
||||
|
@ -108,32 +78,10 @@ func Toggle(options *ToggleOptions) macaron.Handler {
|
|||
ctx.HTML(200, "user/auth/activate")
|
||||
return
|
||||
}
|
||||
if ctx.IsSigned && isAPIPath && ctx.IsBasicAuth {
|
||||
twofa, err := models.GetTwoFactorByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
if models.IsErrTwoFactorNotEnrolled(err) {
|
||||
return // No 2FA enrollment for this user
|
||||
}
|
||||
ctx.Error(500)
|
||||
return
|
||||
}
|
||||
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
|
||||
ok, err := twofa.ValidateTOTP(otpHeader)
|
||||
if err != nil {
|
||||
ctx.Error(500)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "Only signed in user is allowed to call APIs.",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to log in page if auto-signin info is provided and has not signed in.
|
||||
if !options.SignOutRequired && !ctx.IsSigned && !isAPIPath &&
|
||||
if !options.SignOutRequired && !ctx.IsSigned &&
|
||||
len(ctx.GetCookie(setting.CookieUserName)) > 0 {
|
||||
if ctx.Req.URL.Path != "/user/events" {
|
||||
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
|
||||
|
@ -151,3 +99,86 @@ func Toggle(options *ToggleOptions) macaron.Handler {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ToggleAPI returns toggle options as middleware
|
||||
func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) {
|
||||
return func(ctx *APIContext) {
|
||||
// Check prohibit login users.
|
||||
if ctx.IsSigned {
|
||||
if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
|
||||
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "This account is not activated.",
|
||||
})
|
||||
return
|
||||
}
|
||||
if !ctx.User.IsActive || ctx.User.ProhibitLogin {
|
||||
log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr())
|
||||
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "This account is prohibited from signing in, please contact your site administrator.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.User.MustChangePassword {
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to dashboard if user tries to visit any non-login page.
|
||||
if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
|
||||
ctx.Redirect(setting.AppSubURL + "/")
|
||||
return
|
||||
}
|
||||
|
||||
if options.SignInRequired {
|
||||
if !ctx.IsSigned {
|
||||
// Restrict API calls with error message.
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "Only signed in user is allowed to call APIs.",
|
||||
})
|
||||
return
|
||||
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
|
||||
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
|
||||
ctx.HTML(200, "user/auth/activate")
|
||||
return
|
||||
}
|
||||
if ctx.IsSigned && ctx.IsBasicAuth {
|
||||
twofa, err := models.GetTwoFactorByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
if models.IsErrTwoFactorNotEnrolled(err) {
|
||||
return // No 2FA enrollment for this user
|
||||
}
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
|
||||
ok, err := twofa.ValidateTOTP(otpHeader)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "Only signed in user is allowed to call APIs.",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if options.AdminRequired {
|
||||
if !ctx.User.IsAdmin {
|
||||
ctx.JSON(403, map[string]string{
|
||||
"message": "You have no permission to request for this.",
|
||||
})
|
||||
return
|
||||
}
|
||||
ctx.Data["PageIsAdmin"] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
26
modules/context/captcha.go
Normal file
26
modules/context/captcha.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"gitea.com/go-chi/captcha"
|
||||
)
|
||||
|
||||
var imageCaptchaOnce sync.Once
|
||||
var cpt *captcha.Captcha
|
||||
|
||||
// GetImageCaptcha returns global image captcha
|
||||
func GetImageCaptcha() *captcha.Captcha {
|
||||
imageCaptchaOnce.Do(func() {
|
||||
cpt = captcha.NewCaptcha(captcha.Options{
|
||||
SubURL: setting.AppSubURL,
|
||||
})
|
||||
})
|
||||
return cpt
|
||||
}
|
|
@ -6,37 +6,55 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"html"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/auth"
|
||||
"code.gitea.io/gitea/modules/auth/sso"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"gitea.com/macaron/cache"
|
||||
"gitea.com/macaron/csrf"
|
||||
"gitea.com/macaron/i18n"
|
||||
"gitea.com/macaron/macaron"
|
||||
"gitea.com/macaron/session"
|
||||
"gitea.com/go-chi/cache"
|
||||
"gitea.com/go-chi/session"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/unknwon/com"
|
||||
"github.com/unknwon/i18n"
|
||||
"github.com/unrolled/render"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// Render represents a template render
|
||||
type Render interface {
|
||||
TemplateLookup(tmpl string) *template.Template
|
||||
HTML(w io.Writer, status int, name string, binding interface{}, htmlOpt ...render.HTMLOptions) error
|
||||
}
|
||||
|
||||
// Context represents context of a request.
|
||||
type Context struct {
|
||||
*macaron.Context
|
||||
Resp ResponseWriter
|
||||
Req *http.Request
|
||||
Data map[string]interface{}
|
||||
Render Render
|
||||
translation.Locale
|
||||
Cache cache.Cache
|
||||
csrf csrf.CSRF
|
||||
Flash *session.Flash
|
||||
csrf CSRF
|
||||
Flash *middlewares.Flash
|
||||
Session session.Store
|
||||
|
||||
Link string // current request URL
|
||||
|
@ -163,13 +181,22 @@ func (ctx *Context) RedirectToFirst(location ...string) {
|
|||
// HTML calls Context.HTML and converts template name to string.
|
||||
func (ctx *Context) HTML(status int, name base.TplName) {
|
||||
log.Debug("Template: %s", name)
|
||||
ctx.Context.HTML(status, string(name))
|
||||
if err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data); err != nil {
|
||||
ctx.ServerError("Render failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
// HTMLString render content to a string but not http.ResponseWriter
|
||||
func (ctx *Context) HTMLString(name string, data interface{}) (string, error) {
|
||||
var buf strings.Builder
|
||||
err := ctx.Render.HTML(&buf, 200, string(name), data)
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
// RenderWithErr used for page has form validation but need to prompt error to users.
|
||||
func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form interface{}) {
|
||||
if form != nil {
|
||||
auth.AssignForm(form, ctx.Data)
|
||||
middlewares.AssignForm(form, ctx.Data)
|
||||
}
|
||||
ctx.Flash.ErrorMsg = msg
|
||||
ctx.Data["Flash"] = ctx.Flash
|
||||
|
@ -184,7 +211,7 @@ func (ctx *Context) NotFound(title string, err error) {
|
|||
func (ctx *Context) notFoundInternal(title string, err error) {
|
||||
if err != nil {
|
||||
log.ErrorWithSkip(2, "%s: %v", title, err)
|
||||
if macaron.Env != macaron.PROD {
|
||||
if !setting.IsProd() {
|
||||
ctx.Data["ErrorMsg"] = err
|
||||
}
|
||||
}
|
||||
|
@ -203,7 +230,7 @@ func (ctx *Context) ServerError(title string, err error) {
|
|||
func (ctx *Context) serverErrorInternal(title string, err error) {
|
||||
if err != nil {
|
||||
log.ErrorWithSkip(2, "%s: %v", title, err)
|
||||
if macaron.Env != macaron.PROD {
|
||||
if !setting.IsProd() {
|
||||
ctx.Data["ErrorMsg"] = err
|
||||
}
|
||||
}
|
||||
|
@ -224,6 +251,44 @@ func (ctx *Context) NotFoundOrServerError(title string, errck func(error) bool,
|
|||
ctx.serverErrorInternal(title, err)
|
||||
}
|
||||
|
||||
// Header returns a header
|
||||
func (ctx *Context) Header() http.Header {
|
||||
return ctx.Resp.Header()
|
||||
}
|
||||
|
||||
// FIXME: We should differ Query and Form, currently we just use form as query
|
||||
// Currently to be compatible with macaron, we keep it.
|
||||
|
||||
// Query returns request form as string with default
|
||||
func (ctx *Context) Query(key string, defaults ...string) string {
|
||||
return (*Forms)(ctx.Req).MustString(key, defaults...)
|
||||
}
|
||||
|
||||
// QueryTrim returns request form as string with default and trimmed spaces
|
||||
func (ctx *Context) QueryTrim(key string, defaults ...string) string {
|
||||
return (*Forms)(ctx.Req).MustTrimmed(key, defaults...)
|
||||
}
|
||||
|
||||
// QueryStrings returns request form as strings with default
|
||||
func (ctx *Context) QueryStrings(key string, defaults ...[]string) []string {
|
||||
return (*Forms)(ctx.Req).MustStrings(key, defaults...)
|
||||
}
|
||||
|
||||
// QueryInt returns request form as int with default
|
||||
func (ctx *Context) QueryInt(key string, defaults ...int) int {
|
||||
return (*Forms)(ctx.Req).MustInt(key, defaults...)
|
||||
}
|
||||
|
||||
// QueryInt64 returns request form as int64 with default
|
||||
func (ctx *Context) QueryInt64(key string, defaults ...int64) int64 {
|
||||
return (*Forms)(ctx.Req).MustInt64(key, defaults...)
|
||||
}
|
||||
|
||||
// QueryBool returns request form as bool with default
|
||||
func (ctx *Context) QueryBool(key string, defaults ...bool) bool {
|
||||
return (*Forms)(ctx.Req).MustBool(key, defaults...)
|
||||
}
|
||||
|
||||
// HandleText handles HTTP status code
|
||||
func (ctx *Context) HandleText(status int, title string) {
|
||||
if (status/100 == 4) || (status/100 == 5) {
|
||||
|
@ -249,66 +314,324 @@ func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interfa
|
|||
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
|
||||
ctx.Resp.Header().Set("Pragma", "public")
|
||||
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
|
||||
http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)
|
||||
http.ServeContent(ctx.Resp, ctx.Req, name, modtime, r)
|
||||
}
|
||||
|
||||
// PlainText render content as plain text
|
||||
func (ctx *Context) PlainText(status int, bs []byte) {
|
||||
ctx.Resp.WriteHeader(status)
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf8")
|
||||
if _, err := ctx.Resp.Write(bs); err != nil {
|
||||
ctx.ServerError("Render JSON failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeFile serves given file to response.
|
||||
func (ctx *Context) ServeFile(file string, names ...string) {
|
||||
var name string
|
||||
if len(names) > 0 {
|
||||
name = names[0]
|
||||
} else {
|
||||
name = path.Base(file)
|
||||
}
|
||||
ctx.Resp.Header().Set("Content-Description", "File Transfer")
|
||||
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
|
||||
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
|
||||
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
ctx.Resp.Header().Set("Expires", "0")
|
||||
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
|
||||
ctx.Resp.Header().Set("Pragma", "public")
|
||||
http.ServeFile(ctx.Resp, ctx.Req, file)
|
||||
}
|
||||
|
||||
// Error returned an error to web browser
|
||||
func (ctx *Context) Error(status int, contents ...string) {
|
||||
var v = http.StatusText(status)
|
||||
if len(contents) > 0 {
|
||||
v = contents[0]
|
||||
}
|
||||
http.Error(ctx.Resp, v, status)
|
||||
}
|
||||
|
||||
// JSON render content as JSON
|
||||
func (ctx *Context) JSON(status int, content interface{}) {
|
||||
ctx.Resp.WriteHeader(status)
|
||||
ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf8")
|
||||
if err := json.NewEncoder(ctx.Resp).Encode(content); err != nil {
|
||||
ctx.ServerError("Render JSON failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect redirect the request
|
||||
func (ctx *Context) Redirect(location string, status ...int) {
|
||||
code := http.StatusFound
|
||||
if len(status) == 1 {
|
||||
code = status[0]
|
||||
}
|
||||
|
||||
http.Redirect(ctx.Resp, ctx.Req, location, code)
|
||||
}
|
||||
|
||||
// SetCookie set cookies to web browser
|
||||
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
|
||||
middlewares.SetCookie(ctx.Resp, name, value, others...)
|
||||
}
|
||||
|
||||
// GetCookie returns given cookie value from request header.
|
||||
func (ctx *Context) GetCookie(name string) string {
|
||||
return middlewares.GetCookie(ctx.Req, name)
|
||||
}
|
||||
|
||||
// GetSuperSecureCookie returns given cookie value from request header with secret string.
|
||||
func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
|
||||
val := ctx.GetCookie(name)
|
||||
if val == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
text, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
|
||||
text, err = com.AESGCMDecrypt(key, text)
|
||||
return string(text), err == nil
|
||||
}
|
||||
|
||||
// SetSuperSecureCookie sets given cookie value to response header with secret string.
|
||||
func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) {
|
||||
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
|
||||
text, err := com.AESGCMEncrypt(key, []byte(value))
|
||||
if err != nil {
|
||||
panic("error encrypting cookie: " + err.Error())
|
||||
}
|
||||
|
||||
ctx.SetCookie(name, hex.EncodeToString(text), others...)
|
||||
}
|
||||
|
||||
// GetCookieInt returns cookie result in int type.
|
||||
func (ctx *Context) GetCookieInt(name string) int {
|
||||
r, _ := strconv.Atoi(ctx.GetCookie(name))
|
||||
return r
|
||||
}
|
||||
|
||||
// GetCookieInt64 returns cookie result in int64 type.
|
||||
func (ctx *Context) GetCookieInt64(name string) int64 {
|
||||
r, _ := strconv.ParseInt(ctx.GetCookie(name), 10, 64)
|
||||
return r
|
||||
}
|
||||
|
||||
// GetCookieFloat64 returns cookie result in float64 type.
|
||||
func (ctx *Context) GetCookieFloat64(name string) float64 {
|
||||
v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)
|
||||
return v
|
||||
}
|
||||
|
||||
// RemoteAddr returns the client machie ip address
|
||||
func (ctx *Context) RemoteAddr() string {
|
||||
return ctx.Req.RemoteAddr
|
||||
}
|
||||
|
||||
// Params returns the param on route
|
||||
func (ctx *Context) Params(p string) string {
|
||||
s, _ := url.PathUnescape(chi.URLParam(ctx.Req, strings.TrimPrefix(p, ":")))
|
||||
return s
|
||||
}
|
||||
|
||||
// ParamsInt64 returns the param on route as int64
|
||||
func (ctx *Context) ParamsInt64(p string) int64 {
|
||||
v, _ := strconv.ParseInt(ctx.Params(p), 10, 64)
|
||||
return v
|
||||
}
|
||||
|
||||
// SetParams set params into routes
|
||||
func (ctx *Context) SetParams(k, v string) {
|
||||
chiCtx := chi.RouteContext(ctx.Req.Context())
|
||||
chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
|
||||
}
|
||||
|
||||
// Write writes data to webbrowser
|
||||
func (ctx *Context) Write(bs []byte) (int, error) {
|
||||
return ctx.Resp.Write(bs)
|
||||
}
|
||||
|
||||
// Written returns true if there are something sent to web browser
|
||||
func (ctx *Context) Written() bool {
|
||||
return ctx.Resp.Status() > 0
|
||||
}
|
||||
|
||||
// Status writes status code
|
||||
func (ctx *Context) Status(status int) {
|
||||
ctx.Resp.WriteHeader(status)
|
||||
}
|
||||
|
||||
// Handler represents a custom handler
|
||||
type Handler func(*Context)
|
||||
|
||||
// enumerate all content
|
||||
var (
|
||||
contextKey interface{} = "default_context"
|
||||
)
|
||||
|
||||
// WithContext set up install context in request
|
||||
func WithContext(req *http.Request, ctx *Context) *http.Request {
|
||||
return req.WithContext(context.WithValue(req.Context(), contextKey, ctx))
|
||||
}
|
||||
|
||||
// GetContext retrieves install context from request
|
||||
func GetContext(req *http.Request) *Context {
|
||||
return req.Context().Value(contextKey).(*Context)
|
||||
}
|
||||
|
||||
func getCsrfOpts() CsrfOptions {
|
||||
return CsrfOptions{
|
||||
Secret: setting.SecretKey,
|
||||
Cookie: setting.CSRFCookieName,
|
||||
SetCookie: true,
|
||||
Secure: setting.SessionConfig.Secure,
|
||||
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
|
||||
Header: "X-Csrf-Token",
|
||||
CookieDomain: setting.SessionConfig.Domain,
|
||||
CookiePath: setting.SessionConfig.CookiePath,
|
||||
}
|
||||
}
|
||||
|
||||
// Contexter initializes a classic context for a request.
|
||||
func Contexter() macaron.Handler {
|
||||
return func(c *macaron.Context, l i18n.Locale, cache cache.Cache, sess session.Store, f *session.Flash, x csrf.CSRF) {
|
||||
ctx := &Context{
|
||||
Context: c,
|
||||
Cache: cache,
|
||||
csrf: x,
|
||||
Flash: f,
|
||||
Session: sess,
|
||||
Link: setting.AppSubURL + strings.TrimSuffix(c.Req.URL.EscapedPath(), "/"),
|
||||
Repo: &Repository{
|
||||
PullRequest: &PullRequest{},
|
||||
},
|
||||
Org: &Organization{},
|
||||
}
|
||||
ctx.Data["Language"] = ctx.Locale.Language()
|
||||
c.Data["Link"] = ctx.Link
|
||||
ctx.Data["CurrentURL"] = setting.AppSubURL + c.Req.URL.RequestURI()
|
||||
ctx.Data["PageStartTime"] = time.Now()
|
||||
// Quick responses appropriate go-get meta with status 200
|
||||
// regardless of if user have access to the repository,
|
||||
// or the repository does not exist at all.
|
||||
// This is particular a workaround for "go get" command which does not respect
|
||||
// .netrc file.
|
||||
if ctx.Query("go-get") == "1" {
|
||||
ownerName := c.Params(":username")
|
||||
repoName := c.Params(":reponame")
|
||||
trimmedRepoName := strings.TrimSuffix(repoName, ".git")
|
||||
func Contexter() func(next http.Handler) http.Handler {
|
||||
rnd := templates.HTMLRenderer()
|
||||
|
||||
if ownerName == "" || trimmedRepoName == "" {
|
||||
_, _ = c.Write([]byte(`<!doctype html>
|
||||
var c cache.Cache
|
||||
var err error
|
||||
if setting.CacheService.Enabled {
|
||||
c, err = cache.NewCacher(cache.Options{
|
||||
Adapter: setting.CacheService.Adapter,
|
||||
AdapterConfig: setting.CacheService.Conn,
|
||||
Interval: setting.CacheService.Interval,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var csrfOpts = getCsrfOpts()
|
||||
//var flashEncryptionKey, _ = NewSecret()
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
var locale = middlewares.Locale(resp, req)
|
||||
var startTime = time.Now()
|
||||
var link = setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/")
|
||||
var ctx = Context{
|
||||
Resp: NewResponse(resp),
|
||||
Cache: c,
|
||||
Locale: locale,
|
||||
Link: link,
|
||||
Render: rnd,
|
||||
Session: session.GetSession(req),
|
||||
Repo: &Repository{
|
||||
PullRequest: &PullRequest{},
|
||||
},
|
||||
Org: &Organization{},
|
||||
Data: map[string]interface{}{
|
||||
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
|
||||
"PageStartTime": startTime,
|
||||
"TmplLoadTimes": func() string {
|
||||
return time.Since(startTime).String()
|
||||
},
|
||||
"Link": link,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.Req = WithContext(req, &ctx)
|
||||
ctx.csrf = Csrfer(csrfOpts, &ctx)
|
||||
|
||||
// Get flash.
|
||||
flashCookie := ctx.GetCookie("macaron_flash")
|
||||
vals, _ := url.ParseQuery(flashCookie)
|
||||
if len(vals) > 0 {
|
||||
f := &middlewares.Flash{
|
||||
DataStore: &ctx,
|
||||
Values: vals,
|
||||
ErrorMsg: vals.Get("error"),
|
||||
SuccessMsg: vals.Get("success"),
|
||||
InfoMsg: vals.Get("info"),
|
||||
WarningMsg: vals.Get("warning"),
|
||||
}
|
||||
ctx.Data["Flash"] = f
|
||||
}
|
||||
|
||||
f := &middlewares.Flash{
|
||||
DataStore: &ctx,
|
||||
Values: url.Values{},
|
||||
ErrorMsg: "",
|
||||
WarningMsg: "",
|
||||
InfoMsg: "",
|
||||
SuccessMsg: "",
|
||||
}
|
||||
ctx.Resp.Before(func(resp ResponseWriter) {
|
||||
if flash := f.Encode(); len(flash) > 0 {
|
||||
if err == nil {
|
||||
middlewares.SetCookie(resp, "macaron_flash", flash, 0,
|
||||
setting.SessionConfig.CookiePath,
|
||||
middlewares.Domain(setting.SessionConfig.Domain),
|
||||
middlewares.HTTPOnly(true),
|
||||
middlewares.Secure(setting.SessionConfig.Secure),
|
||||
//middlewares.SameSite(opt.SameSite), FIXME: we need a samesite config
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetCookie("macaron_flash", "", -1,
|
||||
setting.SessionConfig.CookiePath,
|
||||
middlewares.Domain(setting.SessionConfig.Domain),
|
||||
middlewares.HTTPOnly(true),
|
||||
middlewares.Secure(setting.SessionConfig.Secure),
|
||||
//middlewares.SameSite(), FIXME: we need a samesite config
|
||||
)
|
||||
})
|
||||
|
||||
ctx.Flash = f
|
||||
|
||||
// Quick responses appropriate go-get meta with status 200
|
||||
// regardless of if user have access to the repository,
|
||||
// or the repository does not exist at all.
|
||||
// This is particular a workaround for "go get" command which does not respect
|
||||
// .netrc file.
|
||||
if ctx.Query("go-get") == "1" {
|
||||
ownerName := ctx.Params(":username")
|
||||
repoName := ctx.Params(":reponame")
|
||||
trimmedRepoName := strings.TrimSuffix(repoName, ".git")
|
||||
|
||||
if ownerName == "" || trimmedRepoName == "" {
|
||||
_, _ = ctx.Write([]byte(`<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
invalid import path
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
c.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
branchName := "master"
|
||||
ctx.Status(400)
|
||||
return
|
||||
}
|
||||
branchName := "master"
|
||||
|
||||
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
|
||||
if err == nil && len(repo.DefaultBranch) > 0 {
|
||||
branchName = repo.DefaultBranch
|
||||
}
|
||||
prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName))
|
||||
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
|
||||
if err == nil && len(repo.DefaultBranch) > 0 {
|
||||
branchName = repo.DefaultBranch
|
||||
}
|
||||
prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName))
|
||||
|
||||
appURL, _ := url.Parse(setting.AppURL)
|
||||
appURL, _ := url.Parse(setting.AppURL)
|
||||
|
||||
insecure := ""
|
||||
if appURL.Scheme == string(setting.HTTP) {
|
||||
insecure = "--insecure "
|
||||
}
|
||||
c.Header().Set("Content-Type", "text/html")
|
||||
c.WriteHeader(http.StatusOK)
|
||||
_, _ = c.Write([]byte(com.Expand(`<!doctype html>
|
||||
insecure := ""
|
||||
if appURL.Scheme == string(setting.HTTP) {
|
||||
insecure = "--insecure "
|
||||
}
|
||||
ctx.Header().Set("Content-Type", "text/html")
|
||||
ctx.Status(http.StatusOK)
|
||||
_, _ = ctx.Write([]byte(com.Expand(`<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="go-import" content="{GoGetImport} git {CloneLink}">
|
||||
|
@ -319,60 +642,72 @@ func Contexter() macaron.Handler {
|
|||
</body>
|
||||
</html>
|
||||
`, map[string]string{
|
||||
"GoGetImport": ComposeGoGetImport(ownerName, trimmedRepoName),
|
||||
"CloneLink": models.ComposeHTTPSCloneURL(ownerName, repoName),
|
||||
"GoDocDirectory": prefix + "{/dir}",
|
||||
"GoDocFile": prefix + "{/dir}/{file}#L{line}",
|
||||
"Insecure": insecure,
|
||||
})))
|
||||
return
|
||||
}
|
||||
|
||||
// Get user from session if logged in.
|
||||
ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req.Request, c.Resp, ctx, ctx.Session)
|
||||
|
||||
if ctx.User != nil {
|
||||
ctx.IsSigned = true
|
||||
ctx.Data["IsSigned"] = ctx.IsSigned
|
||||
ctx.Data["SignedUser"] = ctx.User
|
||||
ctx.Data["SignedUserID"] = ctx.User.ID
|
||||
ctx.Data["SignedUserName"] = ctx.User.Name
|
||||
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
|
||||
} else {
|
||||
ctx.Data["SignedUserID"] = int64(0)
|
||||
ctx.Data["SignedUserName"] = ""
|
||||
}
|
||||
|
||||
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
|
||||
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
|
||||
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
|
||||
ctx.ServerError("ParseMultipartForm", err)
|
||||
"GoGetImport": ComposeGoGetImport(ownerName, trimmedRepoName),
|
||||
"CloneLink": models.ComposeHTTPSCloneURL(ownerName, repoName),
|
||||
"GoDocDirectory": prefix + "{/dir}",
|
||||
"GoDocFile": prefix + "{/dir}/{file}#L{line}",
|
||||
"Insecure": insecure,
|
||||
})))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
|
||||
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
|
||||
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
|
||||
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
|
||||
ctx.ServerError("ParseMultipartForm", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["CsrfToken"] = html.EscapeString(x.GetToken())
|
||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
|
||||
log.Debug("Session ID: %s", sess.ID())
|
||||
log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
|
||||
// Get user from session if logged in.
|
||||
ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req, ctx.Resp, &ctx, ctx.Session)
|
||||
|
||||
ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
|
||||
ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
|
||||
ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
|
||||
if ctx.User != nil {
|
||||
ctx.IsSigned = true
|
||||
ctx.Data["IsSigned"] = ctx.IsSigned
|
||||
ctx.Data["SignedUser"] = ctx.User
|
||||
ctx.Data["SignedUserID"] = ctx.User.ID
|
||||
ctx.Data["SignedUserName"] = ctx.User.Name
|
||||
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
|
||||
} else {
|
||||
ctx.Data["SignedUserID"] = int64(0)
|
||||
ctx.Data["SignedUserName"] = ""
|
||||
}
|
||||
|
||||
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
|
||||
ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
|
||||
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
|
||||
ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
|
||||
|
||||
ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
|
||||
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
|
||||
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
|
||||
ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
|
||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
|
||||
log.Debug("Session ID: %s", ctx.Session.ID())
|
||||
log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
|
||||
|
||||
ctx.Data["ManifestData"] = setting.ManifestData
|
||||
ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
|
||||
ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
|
||||
ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
|
||||
|
||||
c.Map(ctx)
|
||||
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
|
||||
ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
|
||||
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
|
||||
ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
|
||||
|
||||
ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
|
||||
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
|
||||
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
|
||||
|
||||
ctx.Data["ManifestData"] = setting.ManifestData
|
||||
|
||||
ctx.Data["i18n"] = locale
|
||||
ctx.Data["Tr"] = i18n.Tr
|
||||
ctx.Data["Lang"] = locale.Language()
|
||||
ctx.Data["AllLangs"] = translation.AllLangs()
|
||||
for _, lang := range translation.AllLangs() {
|
||||
if lang.Lang == locale.Language() {
|
||||
ctx.Data["LangName"] = lang.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
265
modules/context/csrf.go
Normal file
265
modules/context/csrf.go
Normal file
|
@ -0,0 +1,265 @@
|
|||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
// Copyright 2021 The Gitea Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// a middleware that generates and validates CSRF tokens.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
)
|
||||
|
||||
// CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
|
||||
type CSRF interface {
|
||||
// Return HTTP header to search for token.
|
||||
GetHeaderName() string
|
||||
// Return form value to search for token.
|
||||
GetFormName() string
|
||||
// Return cookie name to search for token.
|
||||
GetCookieName() string
|
||||
// Return cookie path
|
||||
GetCookiePath() string
|
||||
// Return the flag value used for the csrf token.
|
||||
GetCookieHTTPOnly() bool
|
||||
// Return the token.
|
||||
GetToken() string
|
||||
// Validate by token.
|
||||
ValidToken(t string) bool
|
||||
// Error replies to the request with a custom function when ValidToken fails.
|
||||
Error(w http.ResponseWriter)
|
||||
}
|
||||
|
||||
type csrf struct {
|
||||
// Header name value for setting and getting csrf token.
|
||||
Header string
|
||||
// Form name value for setting and getting csrf token.
|
||||
Form string
|
||||
// Cookie name value for setting and getting csrf token.
|
||||
Cookie string
|
||||
//Cookie domain
|
||||
CookieDomain string
|
||||
//Cookie path
|
||||
CookiePath string
|
||||
// Cookie HttpOnly flag value used for the csrf token.
|
||||
CookieHTTPOnly bool
|
||||
// Token generated to pass via header, cookie, or hidden form value.
|
||||
Token string
|
||||
// This value must be unique per user.
|
||||
ID string
|
||||
// Secret used along with the unique id above to generate the Token.
|
||||
Secret string
|
||||
// ErrorFunc is the custom function that replies to the request when ValidToken fails.
|
||||
ErrorFunc func(w http.ResponseWriter)
|
||||
}
|
||||
|
||||
// GetHeaderName returns the name of the HTTP header for csrf token.
|
||||
func (c *csrf) GetHeaderName() string {
|
||||
return c.Header
|
||||
}
|
||||
|
||||
// GetFormName returns the name of the form value for csrf token.
|
||||
func (c *csrf) GetFormName() string {
|
||||
return c.Form
|
||||
}
|
||||
|
||||
// GetCookieName returns the name of the cookie for csrf token.
|
||||
func (c *csrf) GetCookieName() string {
|
||||
return c.Cookie
|
||||
}
|
||||
|
||||
// GetCookiePath returns the path of the cookie for csrf token.
|
||||
func (c *csrf) GetCookiePath() string {
|
||||
return c.CookiePath
|
||||
}
|
||||
|
||||
// GetCookieHTTPOnly returns the flag value used for the csrf token.
|
||||
func (c *csrf) GetCookieHTTPOnly() bool {
|
||||
return c.CookieHTTPOnly
|
||||
}
|
||||
|
||||
// GetToken returns the current token. This is typically used
|
||||
// to populate a hidden form in an HTML template.
|
||||
func (c *csrf) GetToken() string {
|
||||
return c.Token
|
||||
}
|
||||
|
||||
// ValidToken validates the passed token against the existing Secret and ID.
|
||||
func (c *csrf) ValidToken(t string) bool {
|
||||
return ValidToken(t, c.Secret, c.ID, "POST")
|
||||
}
|
||||
|
||||
// Error replies to the request when ValidToken fails.
|
||||
func (c *csrf) Error(w http.ResponseWriter) {
|
||||
c.ErrorFunc(w)
|
||||
}
|
||||
|
||||
// CsrfOptions maintains options to manage behavior of Generate.
|
||||
type CsrfOptions struct {
|
||||
// The global secret value used to generate Tokens.
|
||||
Secret string
|
||||
// HTTP header used to set and get token.
|
||||
Header string
|
||||
// Form value used to set and get token.
|
||||
Form string
|
||||
// Cookie value used to set and get token.
|
||||
Cookie string
|
||||
// Cookie domain.
|
||||
CookieDomain string
|
||||
// Cookie path.
|
||||
CookiePath string
|
||||
CookieHTTPOnly bool
|
||||
// SameSite set the cookie SameSite type
|
||||
SameSite http.SameSite
|
||||
// Key used for getting the unique ID per user.
|
||||
SessionKey string
|
||||
// oldSessionKey saves old value corresponding to SessionKey.
|
||||
oldSessionKey string
|
||||
// If true, send token via X-CSRFToken header.
|
||||
SetHeader bool
|
||||
// If true, send token via _csrf cookie.
|
||||
SetCookie bool
|
||||
// Set the Secure flag to true on the cookie.
|
||||
Secure bool
|
||||
// Disallow Origin appear in request header.
|
||||
Origin bool
|
||||
// The function called when Validate fails.
|
||||
ErrorFunc func(w http.ResponseWriter)
|
||||
// Cookie life time. Default is 0
|
||||
CookieLifeTime int
|
||||
}
|
||||
|
||||
func prepareOptions(options []CsrfOptions) CsrfOptions {
|
||||
var opt CsrfOptions
|
||||
if len(options) > 0 {
|
||||
opt = options[0]
|
||||
}
|
||||
|
||||
// Defaults.
|
||||
if len(opt.Secret) == 0 {
|
||||
opt.Secret = string(com.RandomCreateBytes(10))
|
||||
}
|
||||
if len(opt.Header) == 0 {
|
||||
opt.Header = "X-CSRFToken"
|
||||
}
|
||||
if len(opt.Form) == 0 {
|
||||
opt.Form = "_csrf"
|
||||
}
|
||||
if len(opt.Cookie) == 0 {
|
||||
opt.Cookie = "_csrf"
|
||||
}
|
||||
if len(opt.CookiePath) == 0 {
|
||||
opt.CookiePath = "/"
|
||||
}
|
||||
if len(opt.SessionKey) == 0 {
|
||||
opt.SessionKey = "uid"
|
||||
}
|
||||
opt.oldSessionKey = "_old_" + opt.SessionKey
|
||||
if opt.ErrorFunc == nil {
|
||||
opt.ErrorFunc = func(w http.ResponseWriter) {
|
||||
http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
|
||||
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
|
||||
func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
|
||||
opt = prepareOptions([]CsrfOptions{opt})
|
||||
x := &csrf{
|
||||
Secret: opt.Secret,
|
||||
Header: opt.Header,
|
||||
Form: opt.Form,
|
||||
Cookie: opt.Cookie,
|
||||
CookieDomain: opt.CookieDomain,
|
||||
CookiePath: opt.CookiePath,
|
||||
CookieHTTPOnly: opt.CookieHTTPOnly,
|
||||
ErrorFunc: opt.ErrorFunc,
|
||||
}
|
||||
|
||||
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
|
||||
return x
|
||||
}
|
||||
|
||||
x.ID = "0"
|
||||
uid := ctx.Session.Get(opt.SessionKey)
|
||||
if uid != nil {
|
||||
x.ID = com.ToStr(uid)
|
||||
}
|
||||
|
||||
needsNew := false
|
||||
oldUID := ctx.Session.Get(opt.oldSessionKey)
|
||||
if oldUID == nil || oldUID.(string) != x.ID {
|
||||
needsNew = true
|
||||
_ = ctx.Session.Set(opt.oldSessionKey, x.ID)
|
||||
} else {
|
||||
// If cookie present, map existing token, else generate a new one.
|
||||
if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
|
||||
// FIXME: test coverage.
|
||||
x.Token = val
|
||||
} else {
|
||||
needsNew = true
|
||||
}
|
||||
}
|
||||
|
||||
if needsNew {
|
||||
// FIXME: actionId.
|
||||
x.Token = GenerateToken(x.Secret, x.ID, "POST")
|
||||
if opt.SetCookie {
|
||||
var expires interface{}
|
||||
if opt.CookieLifeTime == 0 {
|
||||
expires = time.Now().AddDate(0, 0, 1)
|
||||
}
|
||||
ctx.SetCookie(opt.Cookie, x.Token, opt.CookieLifeTime, opt.CookiePath, opt.CookieDomain, opt.Secure, opt.CookieHTTPOnly, expires,
|
||||
func(c *http.Cookie) {
|
||||
c.SameSite = opt.SameSite
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if opt.SetHeader {
|
||||
ctx.Resp.Header().Add(opt.Header, x.Token)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
|
||||
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
|
||||
// using ValidToken. If this validation fails, custom Error is sent in the reply.
|
||||
// If neither a header or form value is found, http.StatusBadRequest is sent.
|
||||
func Validate(ctx *Context, x CSRF) {
|
||||
if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 {
|
||||
if !x.ValidToken(token) {
|
||||
ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
|
||||
x.Error(ctx.Resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 {
|
||||
if !x.ValidToken(token) {
|
||||
ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
|
||||
x.Error(ctx.Resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
|
||||
}
|
227
modules/context/form.go
Normal file
227
modules/context/form.go
Normal file
|
@ -0,0 +1,227 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
// Forms a new enhancement of http.Request
|
||||
type Forms http.Request
|
||||
|
||||
// Values returns http.Request values
|
||||
func (f *Forms) Values() url.Values {
|
||||
return (*http.Request)(f).Form
|
||||
}
|
||||
|
||||
// String returns request form as string
|
||||
func (f *Forms) String(key string) (string, error) {
|
||||
return (*http.Request)(f).FormValue(key), nil
|
||||
}
|
||||
|
||||
// Trimmed returns request form as string with trimed spaces left and right
|
||||
func (f *Forms) Trimmed(key string) (string, error) {
|
||||
return strings.TrimSpace((*http.Request)(f).FormValue(key)), nil
|
||||
}
|
||||
|
||||
// Strings returns request form as strings
|
||||
func (f *Forms) Strings(key string) ([]string, error) {
|
||||
if (*http.Request)(f).Form == nil {
|
||||
if err := (*http.Request)(f).ParseMultipartForm(32 << 20); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if v, ok := (*http.Request)(f).Form[key]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return nil, errors.New("not exist")
|
||||
}
|
||||
|
||||
// Escape returns request form as escaped string
|
||||
func (f *Forms) Escape(key string) (string, error) {
|
||||
return template.HTMLEscapeString((*http.Request)(f).FormValue(key)), nil
|
||||
}
|
||||
|
||||
// Int returns request form as int
|
||||
func (f *Forms) Int(key string) (int, error) {
|
||||
return strconv.Atoi((*http.Request)(f).FormValue(key))
|
||||
}
|
||||
|
||||
// Int32 returns request form as int32
|
||||
func (f *Forms) Int32(key string) (int32, error) {
|
||||
v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 32)
|
||||
return int32(v), err
|
||||
}
|
||||
|
||||
// Int64 returns request form as int64
|
||||
func (f *Forms) Int64(key string) (int64, error) {
|
||||
return strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 64)
|
||||
}
|
||||
|
||||
// Uint returns request form as uint
|
||||
func (f *Forms) Uint(key string) (uint, error) {
|
||||
v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
|
||||
return uint(v), err
|
||||
}
|
||||
|
||||
// Uint32 returns request form as uint32
|
||||
func (f *Forms) Uint32(key string) (uint32, error) {
|
||||
v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 32)
|
||||
return uint32(v), err
|
||||
}
|
||||
|
||||
// Uint64 returns request form as uint64
|
||||
func (f *Forms) Uint64(key string) (uint64, error) {
|
||||
return strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
|
||||
}
|
||||
|
||||
// Bool returns request form as bool
|
||||
func (f *Forms) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool((*http.Request)(f).FormValue(key))
|
||||
}
|
||||
|
||||
// Float32 returns request form as float32
|
||||
func (f *Forms) Float32(key string) (float32, error) {
|
||||
v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
|
||||
return float32(v), err
|
||||
}
|
||||
|
||||
// Float64 returns request form as float64
|
||||
func (f *Forms) Float64(key string) (float64, error) {
|
||||
return strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
|
||||
}
|
||||
|
||||
// MustString returns request form as string with default
|
||||
func (f *Forms) MustString(key string, defaults ...string) string {
|
||||
if v := (*http.Request)(f).FormValue(key); len(v) > 0 {
|
||||
return v
|
||||
}
|
||||
if len(defaults) > 0 {
|
||||
return defaults[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// MustTrimmed returns request form as string with default
|
||||
func (f *Forms) MustTrimmed(key string, defaults ...string) string {
|
||||
return strings.TrimSpace(f.MustString(key, defaults...))
|
||||
}
|
||||
|
||||
// MustStrings returns request form as strings with default
|
||||
func (f *Forms) MustStrings(key string, defaults ...[]string) []string {
|
||||
if (*http.Request)(f).Form == nil {
|
||||
if err := (*http.Request)(f).ParseMultipartForm(32 << 20); err != nil {
|
||||
log.Error("ParseMultipartForm: %v", err)
|
||||
return []string{}
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := (*http.Request)(f).Form[key]; ok {
|
||||
return v
|
||||
}
|
||||
if len(defaults) > 0 {
|
||||
return defaults[0]
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// MustEscape returns request form as escaped string with default
|
||||
func (f *Forms) MustEscape(key string, defaults ...string) string {
|
||||
if v := (*http.Request)(f).FormValue(key); len(v) > 0 {
|
||||
return template.HTMLEscapeString(v)
|
||||
}
|
||||
if len(defaults) > 0 {
|
||||
return defaults[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// MustInt returns request form as int with default
|
||||
func (f *Forms) MustInt(key string, defaults ...int) int {
|
||||
v, err := strconv.Atoi((*http.Request)(f).FormValue(key))
|
||||
if len(defaults) > 0 && err != nil {
|
||||
return defaults[0]
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MustInt32 returns request form as int32 with default
|
||||
func (f *Forms) MustInt32(key string, defaults ...int32) int32 {
|
||||
v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 32)
|
||||
if len(defaults) > 0 && err != nil {
|
||||
return defaults[0]
|
||||
}
|
||||
return int32(v)
|
||||
}
|
||||
|
||||
// MustInt64 returns request form as int64 with default
|
||||
func (f *Forms) MustInt64(key string, defaults ...int64) int64 {
|
||||
v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 64)
|
||||
if len(defaults) > 0 && err != nil {
|
||||
return defaults[0]
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MustUint returns request form as uint with default
|
||||
func (f *Forms) MustUint(key string, defaults ...uint) uint {
|
||||
v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
|
||||
if len(defaults) > 0 && err != nil {
|
||||
return defaults[0]
|
||||
}
|
||||
return uint(v)
|
||||
}
|
||||
|
||||
// MustUint32 returns request form as uint32 with default
|
||||
func (f *Forms) MustUint32(key string, defaults ...uint32) uint32 {
|
||||
v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 32)
|
||||
if len(defaults) > 0 && err != nil {
|
||||
return defaults[0]
|
||||
}
|
||||
return uint32(v)
|
||||
}
|
||||
|
||||
// MustUint64 returns request form as uint64 with default
|
||||
func (f *Forms) MustUint64(key string, defaults ...uint64) uint64 {
|
||||
v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
|
||||
if len(defaults) > 0 && err != nil {
|
||||
return defaults[0]
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MustFloat32 returns request form as float32 with default
|
||||
func (f *Forms) MustFloat32(key string, defaults ...float32) float32 {
|
||||
v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 32)
|
||||
if len(defaults) > 0 && err != nil {
|
||||
return defaults[0]
|
||||
}
|
||||
return float32(v)
|
||||
}
|
||||
|
||||
// MustFloat64 returns request form as float64 with default
|
||||
func (f *Forms) MustFloat64(key string, defaults ...float64) float64 {
|
||||
v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
|
||||
if len(defaults) > 0 && err != nil {
|
||||
return defaults[0]
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MustBool returns request form as bool with default
|
||||
func (f *Forms) MustBool(key string, defaults ...bool) bool {
|
||||
v, err := strconv.ParseBool((*http.Request)(f).FormValue(key))
|
||||
if len(defaults) > 0 && err != nil {
|
||||
return defaults[0]
|
||||
}
|
||||
return v
|
||||
}
|
|
@ -10,8 +10,6 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"gitea.com/macaron/macaron"
|
||||
)
|
||||
|
||||
// Organization contains organization context
|
||||
|
@ -173,7 +171,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
|||
}
|
||||
|
||||
// OrgAssignment returns a macaron middleware to handle organization assignment
|
||||
func OrgAssignment(args ...bool) macaron.Handler {
|
||||
func OrgAssignment(args ...bool) func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
HandleOrgAssignment(ctx, args...)
|
||||
}
|
||||
|
|
|
@ -7,12 +7,10 @@ package context
|
|||
import (
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"gitea.com/macaron/macaron"
|
||||
)
|
||||
|
||||
// RequireRepoAdmin returns a macaron middleware for requiring repository admin permission
|
||||
func RequireRepoAdmin() macaron.Handler {
|
||||
func RequireRepoAdmin() func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
if !ctx.IsSigned || !ctx.Repo.IsAdmin() {
|
||||
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
|
||||
|
@ -22,7 +20,7 @@ func RequireRepoAdmin() macaron.Handler {
|
|||
}
|
||||
|
||||
// RequireRepoWriter returns a macaron middleware for requiring repository write to the specify unitType
|
||||
func RequireRepoWriter(unitType models.UnitType) macaron.Handler {
|
||||
func RequireRepoWriter(unitType models.UnitType) func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
if !ctx.Repo.CanWrite(unitType) {
|
||||
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
|
||||
|
@ -32,7 +30,7 @@ func RequireRepoWriter(unitType models.UnitType) macaron.Handler {
|
|||
}
|
||||
|
||||
// RequireRepoWriterOr returns a macaron middleware for requiring repository write to one of the unit permission
|
||||
func RequireRepoWriterOr(unitTypes ...models.UnitType) macaron.Handler {
|
||||
func RequireRepoWriterOr(unitTypes ...models.UnitType) func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
for _, unitType := range unitTypes {
|
||||
if ctx.Repo.CanWrite(unitType) {
|
||||
|
@ -44,7 +42,7 @@ func RequireRepoWriterOr(unitTypes ...models.UnitType) macaron.Handler {
|
|||
}
|
||||
|
||||
// RequireRepoReader returns a macaron middleware for requiring repository read to the specify unitType
|
||||
func RequireRepoReader(unitType models.UnitType) macaron.Handler {
|
||||
func RequireRepoReader(unitType models.UnitType) func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
if !ctx.Repo.CanRead(unitType) {
|
||||
if log.IsTrace() {
|
||||
|
@ -70,7 +68,7 @@ func RequireRepoReader(unitType models.UnitType) macaron.Handler {
|
|||
}
|
||||
|
||||
// RequireRepoReaderOr returns a macaron middleware for requiring repository write to one of the unit permission
|
||||
func RequireRepoReaderOr(unitTypes ...models.UnitType) macaron.Handler {
|
||||
func RequireRepoReaderOr(unitTypes ...models.UnitType) func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
for _, unitType := range unitTypes {
|
||||
if ctx.Repo.CanRead(unitType) {
|
||||
|
|
45
modules/context/private.go
Normal file
45
modules/context/private.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// PrivateContext represents a context for private routes
|
||||
type PrivateContext struct {
|
||||
*Context
|
||||
}
|
||||
|
||||
var (
|
||||
privateContextKey interface{} = "default_private_context"
|
||||
)
|
||||
|
||||
// WithPrivateContext set up private context in request
|
||||
func WithPrivateContext(req *http.Request, ctx *PrivateContext) *http.Request {
|
||||
return req.WithContext(context.WithValue(req.Context(), privateContextKey, ctx))
|
||||
}
|
||||
|
||||
// GetPrivateContext returns a context for Private routes
|
||||
func GetPrivateContext(req *http.Request) *PrivateContext {
|
||||
return req.Context().Value(privateContextKey).(*PrivateContext)
|
||||
}
|
||||
|
||||
// PrivateContexter returns apicontext as macaron middleware
|
||||
func PrivateContexter() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := &PrivateContext{
|
||||
Context: &Context{
|
||||
Resp: NewResponse(w),
|
||||
Data: map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
ctx.Req = WithPrivateContext(req, ctx)
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ package context
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
@ -21,7 +22,6 @@ import (
|
|||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"gitea.com/macaron/macaron"
|
||||
"github.com/editorconfig/editorconfig-core-go/v2"
|
||||
"github.com/unknwon/com"
|
||||
)
|
||||
|
@ -81,7 +81,7 @@ func (r *Repository) CanCreateBranch() bool {
|
|||
}
|
||||
|
||||
// RepoMustNotBeArchived checks if a repo is archived
|
||||
func RepoMustNotBeArchived() macaron.Handler {
|
||||
func RepoMustNotBeArchived() func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
if ctx.Repo.Repository.IsArchived {
|
||||
ctx.NotFound("IsArchived", fmt.Errorf(ctx.Tr("repo.archive.title")))
|
||||
|
@ -374,7 +374,7 @@ func repoAssignment(ctx *Context, repo *models.Repository) {
|
|||
}
|
||||
|
||||
// RepoIDAssignment returns a macaron handler which assigns the repo to the context.
|
||||
func RepoIDAssignment() macaron.Handler {
|
||||
func RepoIDAssignment() func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
repoID := ctx.ParamsInt64(":repoid")
|
||||
|
||||
|
@ -394,223 +394,220 @@ func RepoIDAssignment() macaron.Handler {
|
|||
}
|
||||
|
||||
// RepoAssignment returns a macaron to handle repository assignment
|
||||
func RepoAssignment() macaron.Handler {
|
||||
return func(ctx *Context) {
|
||||
var (
|
||||
owner *models.User
|
||||
err error
|
||||
)
|
||||
func RepoAssignment() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var (
|
||||
owner *models.User
|
||||
err error
|
||||
ctx = GetContext(req)
|
||||
)
|
||||
|
||||
userName := ctx.Params(":username")
|
||||
repoName := ctx.Params(":reponame")
|
||||
userName := ctx.Params(":username")
|
||||
repoName := ctx.Params(":reponame")
|
||||
repoName = strings.TrimSuffix(repoName, ".git")
|
||||
|
||||
// Check if the user is the same as the repository owner
|
||||
if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
|
||||
owner = ctx.User
|
||||
} else {
|
||||
owner, err = models.GetUserByName(userName)
|
||||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
redirectUserID, err := models.LookupUserRedirect(userName)
|
||||
if err == nil {
|
||||
RedirectToUser(ctx, userName, redirectUserID)
|
||||
} else if models.IsErrUserRedirectNotExist(err) {
|
||||
// Check if the user is the same as the repository owner
|
||||
if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
|
||||
owner = ctx.User
|
||||
} else {
|
||||
owner, err = models.GetUserByName(userName)
|
||||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
if ctx.Query("go-get") == "1" {
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
return
|
||||
}
|
||||
ctx.NotFound("GetUserByName", nil)
|
||||
} else {
|
||||
ctx.ServerError("LookupUserRedirect", err)
|
||||
ctx.ServerError("GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Repo.Owner = owner
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
|
||||
// Get repository.
|
||||
repo, err := models.GetRepositoryByName(owner.ID, repoName)
|
||||
if err != nil {
|
||||
if models.IsErrRepoNotExist(err) {
|
||||
redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
|
||||
if err == nil {
|
||||
RedirectToRepo(ctx, redirectRepoID)
|
||||
} else if models.IsErrRepoRedirectNotExist(err) {
|
||||
if ctx.Query("go-get") == "1" {
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
return
|
||||
}
|
||||
ctx.NotFound("GetRepositoryByName", nil)
|
||||
} else {
|
||||
ctx.ServerError("LookupRepoRedirect", err)
|
||||
}
|
||||
} else {
|
||||
ctx.ServerError("GetUserByName", err)
|
||||
ctx.ServerError("GetRepositoryByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Repo.Owner = owner
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
repo.Owner = owner
|
||||
|
||||
// Get repository.
|
||||
repo, err := models.GetRepositoryByName(owner.ID, repoName)
|
||||
if err != nil {
|
||||
if models.IsErrRepoNotExist(err) {
|
||||
redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
|
||||
if err == nil {
|
||||
RedirectToRepo(ctx, redirectRepoID)
|
||||
} else if models.IsErrRepoRedirectNotExist(err) {
|
||||
if ctx.Query("go-get") == "1" {
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
return
|
||||
}
|
||||
ctx.NotFound("GetRepositoryByName", nil)
|
||||
} else {
|
||||
ctx.ServerError("LookupRepoRedirect", err)
|
||||
}
|
||||
} else {
|
||||
ctx.ServerError("GetRepositoryByName", err)
|
||||
repoAssignment(ctx, repo)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
repo.Owner = owner
|
||||
|
||||
repoAssignment(ctx, repo)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
ctx.Repo.RepoLink = repo.Link()
|
||||
ctx.Data["RepoLink"] = ctx.Repo.RepoLink
|
||||
ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
|
||||
|
||||
ctx.Repo.RepoLink = repo.Link()
|
||||
ctx.Data["RepoLink"] = ctx.Repo.RepoLink
|
||||
ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
|
||||
unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
|
||||
if err == nil {
|
||||
ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
|
||||
}
|
||||
|
||||
unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
|
||||
if err == nil {
|
||||
ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
|
||||
}
|
||||
ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
|
||||
IncludeTags: true,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleaseCountByRepoID", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleaseCountByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
|
||||
IncludeTags: true,
|
||||
ctx.Data["Title"] = owner.Name + "/" + repo.Name
|
||||
ctx.Data["Repository"] = repo
|
||||
ctx.Data["Owner"] = ctx.Repo.Repository.Owner
|
||||
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
|
||||
ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
|
||||
ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
|
||||
ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
|
||||
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
|
||||
ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
|
||||
|
||||
if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
|
||||
ctx.ServerError("CanUserFork", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["DisableSSH"] = setting.SSH.Disabled
|
||||
ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
|
||||
ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
|
||||
ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||
ctx.Data["CloneLink"] = repo.CloneLink()
|
||||
ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
|
||||
|
||||
if ctx.IsSigned {
|
||||
ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
|
||||
ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
|
||||
}
|
||||
|
||||
if repo.IsFork {
|
||||
RetrieveBaseRepo(ctx, repo)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if repo.IsGenerated() {
|
||||
RetrieveTemplateRepo(ctx, repo)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Disable everything when the repo is being created
|
||||
if ctx.Repo.Repository.IsBeingCreated() {
|
||||
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
|
||||
return
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
|
||||
if err != nil {
|
||||
ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
|
||||
// We opened it, we should close it
|
||||
defer func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Stop at this point when the repo is empty.
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
|
||||
next.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
tags, err := ctx.Repo.GitRepo.GetTags()
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTags", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Tags"] = tags
|
||||
|
||||
brs, err := ctx.Repo.GitRepo.GetBranches()
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranches", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Branches"] = brs
|
||||
ctx.Data["BranchesCount"] = len(brs)
|
||||
|
||||
ctx.Data["TagName"] = ctx.Repo.TagName
|
||||
|
||||
// If not branch selected, try default one.
|
||||
// If default branch doesn't exists, fall back to some other branch.
|
||||
if len(ctx.Repo.BranchName) == 0 {
|
||||
if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
|
||||
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
|
||||
} else if len(brs) > 0 {
|
||||
ctx.Repo.BranchName = brs[0]
|
||||
}
|
||||
}
|
||||
ctx.Data["BranchName"] = ctx.Repo.BranchName
|
||||
ctx.Data["CommitID"] = ctx.Repo.CommitID
|
||||
|
||||
// People who have push access or have forked repository can propose a new pull request.
|
||||
canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
|
||||
canCompare := false
|
||||
|
||||
// Pull request is allowed if this is a fork repository
|
||||
// and base repository accepts pull requests.
|
||||
if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
|
||||
canCompare = true
|
||||
ctx.Data["BaseRepo"] = repo.BaseRepo
|
||||
ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
|
||||
ctx.Repo.PullRequest.Allowed = canPush
|
||||
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
|
||||
} else if repo.AllowsPulls() {
|
||||
// Or, this is repository accepts pull requests between branches.
|
||||
canCompare = true
|
||||
ctx.Data["BaseRepo"] = repo
|
||||
ctx.Repo.PullRequest.BaseRepo = repo
|
||||
ctx.Repo.PullRequest.Allowed = canPush
|
||||
ctx.Repo.PullRequest.SameRepo = true
|
||||
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
|
||||
}
|
||||
ctx.Data["CanCompareOrPull"] = canCompare
|
||||
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
|
||||
|
||||
if ctx.Query("go-get") == "1" {
|
||||
ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
|
||||
prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
|
||||
ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
|
||||
ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
|
||||
}
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleaseCountByRepoID", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleaseCountByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = owner.Name + "/" + repo.Name
|
||||
ctx.Data["Repository"] = repo
|
||||
ctx.Data["Owner"] = ctx.Repo.Repository.Owner
|
||||
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
|
||||
ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
|
||||
ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
|
||||
ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
|
||||
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
|
||||
ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
|
||||
|
||||
if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
|
||||
ctx.ServerError("CanUserFork", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["DisableSSH"] = setting.SSH.Disabled
|
||||
ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
|
||||
ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
|
||||
ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
|
||||
ctx.Data["CloneLink"] = repo.CloneLink()
|
||||
ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
|
||||
|
||||
if ctx.IsSigned {
|
||||
ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
|
||||
ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
|
||||
}
|
||||
|
||||
if repo.IsFork {
|
||||
RetrieveBaseRepo(ctx, repo)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if repo.IsGenerated() {
|
||||
RetrieveTemplateRepo(ctx, repo)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Disable everything when the repo is being created
|
||||
if ctx.Repo.Repository.IsBeingCreated() {
|
||||
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
|
||||
return
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
|
||||
if err != nil {
|
||||
ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
|
||||
// We opened it, we should close it
|
||||
defer func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Stop at this point when the repo is empty.
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
|
||||
tags, err := ctx.Repo.GitRepo.GetTags()
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTags", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Tags"] = tags
|
||||
|
||||
brs, err := ctx.Repo.GitRepo.GetBranches()
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranches", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Branches"] = brs
|
||||
ctx.Data["BranchesCount"] = len(brs)
|
||||
|
||||
ctx.Data["TagName"] = ctx.Repo.TagName
|
||||
|
||||
// If not branch selected, try default one.
|
||||
// If default branch doesn't exists, fall back to some other branch.
|
||||
if len(ctx.Repo.BranchName) == 0 {
|
||||
if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
|
||||
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
|
||||
} else if len(brs) > 0 {
|
||||
ctx.Repo.BranchName = brs[0]
|
||||
}
|
||||
}
|
||||
ctx.Data["BranchName"] = ctx.Repo.BranchName
|
||||
ctx.Data["CommitID"] = ctx.Repo.CommitID
|
||||
|
||||
// People who have push access or have forked repository can propose a new pull request.
|
||||
canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
|
||||
canCompare := false
|
||||
|
||||
// Pull request is allowed if this is a fork repository
|
||||
// and base repository accepts pull requests.
|
||||
if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
|
||||
canCompare = true
|
||||
ctx.Data["BaseRepo"] = repo.BaseRepo
|
||||
ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
|
||||
ctx.Repo.PullRequest.Allowed = canPush
|
||||
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
|
||||
} else if repo.AllowsPulls() {
|
||||
// Or, this is repository accepts pull requests between branches.
|
||||
canCompare = true
|
||||
ctx.Data["BaseRepo"] = repo
|
||||
ctx.Repo.PullRequest.BaseRepo = repo
|
||||
ctx.Repo.PullRequest.Allowed = canPush
|
||||
ctx.Repo.PullRequest.SameRepo = true
|
||||
ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
|
||||
}
|
||||
ctx.Data["CanCompareOrPull"] = canCompare
|
||||
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
|
||||
|
||||
if ctx.Query("go-get") == "1" {
|
||||
ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
|
||||
prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
|
||||
ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
|
||||
ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
|
||||
}
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -636,7 +633,7 @@ const (
|
|||
|
||||
// RepoRef handles repository reference names when the ref name is not
|
||||
// explicitly given
|
||||
func RepoRef() macaron.Handler {
|
||||
func RepoRef() func(http.Handler) http.Handler {
|
||||
// since no ref name is explicitly specified, ok to just use branch
|
||||
return RepoRefByType(RepoRefBranch)
|
||||
}
|
||||
|
@ -715,132 +712,135 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
|
|||
|
||||
// RepoRefByType handles repository reference name for a specific type
|
||||
// of repository reference
|
||||
func RepoRefByType(refType RepoRefType) macaron.Handler {
|
||||
return func(ctx *Context) {
|
||||
// Empty repository does not have reference information.
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
refName string
|
||||
err error
|
||||
)
|
||||
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
|
||||
ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
|
||||
func RepoRefByType(refType RepoRefType) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := GetContext(req)
|
||||
// Empty repository does not have reference information.
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
return
|
||||
}
|
||||
// We opened it, we should close it
|
||||
defer func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Get default branch.
|
||||
if len(ctx.Params("*")) == 0 {
|
||||
refName = ctx.Repo.Repository.DefaultBranch
|
||||
ctx.Repo.BranchName = refName
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
brs, err := ctx.Repo.GitRepo.GetBranches()
|
||||
var (
|
||||
refName string
|
||||
err error
|
||||
)
|
||||
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
|
||||
ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranches", err)
|
||||
return
|
||||
} else if len(brs) == 0 {
|
||||
err = fmt.Errorf("No branches in non-empty repository %s",
|
||||
ctx.Repo.GitRepo.Path)
|
||||
ctx.ServerError("GetBranches", err)
|
||||
ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
|
||||
return
|
||||
}
|
||||
refName = brs[0]
|
||||
// We opened it, we should close it
|
||||
defer func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranchCommit", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
ctx.Repo.IsViewBranch = true
|
||||
|
||||
} else {
|
||||
refName = getRefName(ctx, refType)
|
||||
ctx.Repo.BranchName = refName
|
||||
if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
ctx.Repo.IsViewBranch = true
|
||||
|
||||
// Get default branch.
|
||||
if len(ctx.Params("*")) == 0 {
|
||||
refName = ctx.Repo.Repository.DefaultBranch
|
||||
ctx.Repo.BranchName = refName
|
||||
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
brs, err := ctx.Repo.GitRepo.GetBranches()
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranches", err)
|
||||
return
|
||||
} else if len(brs) == 0 {
|
||||
err = fmt.Errorf("No branches in non-empty repository %s",
|
||||
ctx.Repo.GitRepo.Path)
|
||||
ctx.ServerError("GetBranches", err)
|
||||
return
|
||||
}
|
||||
refName = brs[0]
|
||||
}
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranchCommit", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
ctx.Repo.IsViewBranch = true
|
||||
|
||||
} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
|
||||
ctx.Repo.IsViewTag = true
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTagCommit", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) >= 7 && len(refName) <= 40 {
|
||||
ctx.Repo.IsViewCommit = true
|
||||
ctx.Repo.CommitID = refName
|
||||
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
ctx.NotFound("GetCommit", err)
|
||||
return
|
||||
}
|
||||
// If short commit ID add canonical link header
|
||||
if len(refName) < 40 {
|
||||
ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
|
||||
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
|
||||
}
|
||||
} else {
|
||||
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
|
||||
return
|
||||
refName = getRefName(ctx, refType)
|
||||
ctx.Repo.BranchName = refName
|
||||
if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
ctx.Repo.IsViewBranch = true
|
||||
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranchCommit", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
|
||||
} else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
|
||||
ctx.Repo.IsViewTag = true
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTagCommit", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if len(refName) >= 7 && len(refName) <= 40 {
|
||||
ctx.Repo.IsViewCommit = true
|
||||
ctx.Repo.CommitID = refName
|
||||
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
ctx.NotFound("GetCommit", err)
|
||||
return
|
||||
}
|
||||
// If short commit ID add canonical link header
|
||||
if len(refName) < 40 {
|
||||
ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
|
||||
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
|
||||
}
|
||||
} else {
|
||||
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
|
||||
return
|
||||
}
|
||||
|
||||
if refType == RepoRefLegacy {
|
||||
// redirect from old URL scheme to new URL scheme
|
||||
ctx.Redirect(path.Join(
|
||||
setting.AppSubURL,
|
||||
strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")),
|
||||
ctx.Repo.BranchNameSubURL(),
|
||||
ctx.Repo.TreePath))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if refType == RepoRefLegacy {
|
||||
// redirect from old URL scheme to new URL scheme
|
||||
ctx.Redirect(path.Join(
|
||||
setting.AppSubURL,
|
||||
strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")),
|
||||
ctx.Repo.BranchNameSubURL(),
|
||||
ctx.Repo.TreePath))
|
||||
ctx.Data["BranchName"] = ctx.Repo.BranchName
|
||||
ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
|
||||
ctx.Data["CommitID"] = ctx.Repo.CommitID
|
||||
ctx.Data["TreePath"] = ctx.Repo.TreePath
|
||||
ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
|
||||
ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
|
||||
ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
|
||||
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
|
||||
|
||||
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitsCount", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
||||
|
||||
ctx.Data["BranchName"] = ctx.Repo.BranchName
|
||||
ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
|
||||
ctx.Data["CommitID"] = ctx.Repo.CommitID
|
||||
ctx.Data["TreePath"] = ctx.Repo.TreePath
|
||||
ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
|
||||
ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
|
||||
ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
|
||||
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
|
||||
|
||||
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitsCount", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
||||
|
||||
ctx.Next()
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GitHookService checks if repository Git hooks service has been enabled.
|
||||
func GitHookService() macaron.Handler {
|
||||
func GitHookService() func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
if !ctx.User.CanEditGitHook() {
|
||||
ctx.NotFound("GitHookService", nil)
|
||||
|
@ -850,7 +850,7 @@ func GitHookService() macaron.Handler {
|
|||
}
|
||||
|
||||
// UnitTypes returns a macaron middleware to set unit types to context variables.
|
||||
func UnitTypes() macaron.Handler {
|
||||
func UnitTypes() func(ctx *Context) {
|
||||
return func(ctx *Context) {
|
||||
ctx.Data["UnitTypeCode"] = models.UnitTypeCode
|
||||
ctx.Data["UnitTypeIssues"] = models.UnitTypeIssues
|
||||
|
|
|
@ -11,6 +11,7 @@ type ResponseWriter interface {
|
|||
http.ResponseWriter
|
||||
Flush()
|
||||
Status() int
|
||||
Before(func(ResponseWriter))
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -20,11 +21,19 @@ var (
|
|||
// Response represents a response
|
||||
type Response struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
status int
|
||||
befores []func(ResponseWriter)
|
||||
beforeExecuted bool
|
||||
}
|
||||
|
||||
// Write writes bytes to HTTP endpoint
|
||||
func (r *Response) Write(bs []byte) (int, error) {
|
||||
if !r.beforeExecuted {
|
||||
for _, before := range r.befores {
|
||||
before(r)
|
||||
}
|
||||
r.beforeExecuted = true
|
||||
}
|
||||
size, err := r.ResponseWriter.Write(bs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
@ -37,6 +46,12 @@ func (r *Response) Write(bs []byte) (int, error) {
|
|||
|
||||
// WriteHeader write status code
|
||||
func (r *Response) WriteHeader(statusCode int) {
|
||||
if !r.beforeExecuted {
|
||||
for _, before := range r.befores {
|
||||
before(r)
|
||||
}
|
||||
r.beforeExecuted = true
|
||||
}
|
||||
r.status = statusCode
|
||||
r.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
@ -53,10 +68,20 @@ func (r *Response) Status() int {
|
|||
return r.status
|
||||
}
|
||||
|
||||
// Before allows for a function to be called before the ResponseWriter has been written to. This is
|
||||
// useful for setting headers or any other operations that must happen before a response has been written.
|
||||
func (r *Response) Before(f func(ResponseWriter)) {
|
||||
r.befores = append(r.befores, f)
|
||||
}
|
||||
|
||||
// NewResponse creates a response
|
||||
func NewResponse(resp http.ResponseWriter) *Response {
|
||||
if v, ok := resp.(*Response); ok {
|
||||
return v
|
||||
}
|
||||
return &Response{resp, 0}
|
||||
return &Response{
|
||||
ResponseWriter: resp,
|
||||
status: 0,
|
||||
befores: make([]func(ResponseWriter), 0),
|
||||
}
|
||||
}
|
||||
|
|
100
modules/context/secret.go
Normal file
100
modules/context/secret.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// NewSecret creates a new secret
|
||||
func NewSecret() (string, error) {
|
||||
return NewSecretWithLength(32)
|
||||
}
|
||||
|
||||
// NewSecretWithLength creates a new secret for a given length
|
||||
func NewSecretWithLength(length int64) (string, error) {
|
||||
return randomString(length)
|
||||
}
|
||||
|
||||
func randomBytes(len int64) ([]byte, error) {
|
||||
b := make([]byte, len)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func randomString(len int64) (string, error) {
|
||||
b, err := randomBytes(len)
|
||||
return base64.URLEncoding.EncodeToString(b), err
|
||||
}
|
||||
|
||||
// AesEncrypt encrypts text and given key with AES.
|
||||
func AesEncrypt(key, text []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := base64.StdEncoding.EncodeToString(text)
|
||||
ciphertext := make([]byte, aes.BlockSize+len(b))
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfb := cipher.NewCFBEncrypter(block, iv)
|
||||
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// AesDecrypt decrypts text and given key with AES.
|
||||
func AesDecrypt(key, text []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(text) < aes.BlockSize {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
iv := text[:aes.BlockSize]
|
||||
text = text[aes.BlockSize:]
|
||||
cfb := cipher.NewCFBDecrypter(block, iv)
|
||||
cfb.XORKeyStream(text, text)
|
||||
data, err := base64.StdEncoding.DecodeString(string(text))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// EncryptSecret encrypts a string with given key into a hex string
|
||||
func EncryptSecret(key string, str string) (string, error) {
|
||||
keyHash := sha256.Sum256([]byte(key))
|
||||
plaintext := []byte(str)
|
||||
ciphertext, err := AesEncrypt(keyHash[:], plaintext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
// DecryptSecret decrypts a previously encrypted hex string
|
||||
func DecryptSecret(key string, cipherhex string) (string, error) {
|
||||
keyHash := sha256.Sum256([]byte(key))
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(cipherhex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
plaintext, err := AesDecrypt(keyHash[:], ciphertext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(plaintext), nil
|
||||
}
|
98
modules/context/xsrf.go
Normal file
98
modules/context/xsrf.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
// Copyright 2014 The Macaron Authors
|
||||
// Copyright 2020 The Gitea Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timeout represents the duration that XSRF tokens are valid.
|
||||
// It is exported so clients may set cookie timeouts that match generated tokens.
|
||||
const Timeout = 24 * time.Hour
|
||||
|
||||
// clean sanitizes a string for inclusion in a token by replacing all ":"s.
|
||||
func clean(s string) string {
|
||||
return strings.ReplaceAll(s, ":", "_")
|
||||
}
|
||||
|
||||
// GenerateToken returns a URL-safe secure XSRF token that expires in 24 hours.
|
||||
//
|
||||
// key is a secret key for your application.
|
||||
// userID is a unique identifier for the user.
|
||||
// actionID is the action the user is taking (e.g. POSTing to a particular path).
|
||||
func GenerateToken(key, userID, actionID string) string {
|
||||
return generateTokenAtTime(key, userID, actionID, time.Now())
|
||||
}
|
||||
|
||||
// generateTokenAtTime is like Generate, but returns a token that expires 24 hours from now.
|
||||
func generateTokenAtTime(key, userID, actionID string, now time.Time) string {
|
||||
h := hmac.New(sha1.New, []byte(key))
|
||||
fmt.Fprintf(h, "%s:%s:%d", clean(userID), clean(actionID), now.UnixNano())
|
||||
tok := fmt.Sprintf("%s:%d", h.Sum(nil), now.UnixNano())
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(tok))
|
||||
}
|
||||
|
||||
// ValidToken returns true if token is a valid, unexpired token returned by Generate.
|
||||
func ValidToken(token, key, userID, actionID string) bool {
|
||||
return validTokenAtTime(token, key, userID, actionID, time.Now())
|
||||
}
|
||||
|
||||
// validTokenAtTime is like Valid, but it uses now to check if the token is expired.
|
||||
func validTokenAtTime(token, key, userID, actionID string, now time.Time) bool {
|
||||
// Decode the token.
|
||||
data, err := base64.RawURLEncoding.DecodeString(token)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Extract the issue time of the token.
|
||||
sep := bytes.LastIndex(data, []byte{':'})
|
||||
if sep < 0 {
|
||||
return false
|
||||
}
|
||||
nanos, err := strconv.ParseInt(string(data[sep+1:]), 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
issueTime := time.Unix(0, nanos)
|
||||
|
||||
// Check that the token is not expired.
|
||||
if now.Sub(issueTime) >= Timeout {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check that the token is not from the future.
|
||||
// Allow 1 minute grace period in case the token is being verified on a
|
||||
// machine whose clock is behind the machine that issued the token.
|
||||
if issueTime.After(now.Add(1 * time.Minute)) {
|
||||
return false
|
||||
}
|
||||
|
||||
expected := generateTokenAtTime(key, userID, actionID, issueTime)
|
||||
|
||||
// Check that the token matches the expected value.
|
||||
// Use constant time comparison to avoid timing attacks.
|
||||
return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
|
||||
}
|
90
modules/context/xsrf_test.go
Normal file
90
modules/context/xsrf_test.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
// Copyright 2014 The Macaron Authors
|
||||
// Copyright 2020 The Gitea Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
key = "quay"
|
||||
userID = "12345678"
|
||||
actionID = "POST /form"
|
||||
)
|
||||
|
||||
var (
|
||||
now = time.Now()
|
||||
oneMinuteFromNow = now.Add(1 * time.Minute)
|
||||
)
|
||||
|
||||
func Test_ValidToken(t *testing.T) {
|
||||
t.Run("Validate token", func(t *testing.T) {
|
||||
tok := generateTokenAtTime(key, userID, actionID, now)
|
||||
assert.True(t, validTokenAtTime(tok, key, userID, actionID, oneMinuteFromNow))
|
||||
assert.True(t, validTokenAtTime(tok, key, userID, actionID, now.Add(Timeout-1*time.Nanosecond)))
|
||||
assert.True(t, validTokenAtTime(tok, key, userID, actionID, now.Add(-1*time.Minute)))
|
||||
})
|
||||
}
|
||||
|
||||
// Test_SeparatorReplacement tests that separators are being correctly substituted
|
||||
func Test_SeparatorReplacement(t *testing.T) {
|
||||
t.Run("Test two separator replacements", func(t *testing.T) {
|
||||
assert.NotEqual(t, generateTokenAtTime("foo:bar", "baz", "wah", now),
|
||||
generateTokenAtTime("foo", "bar:baz", "wah", now))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_InvalidToken(t *testing.T) {
|
||||
t.Run("Test invalid tokens", func(t *testing.T) {
|
||||
invalidTokenTests := []struct {
|
||||
name, key, userID, actionID string
|
||||
t time.Time
|
||||
}{
|
||||
{"Bad key", "foobar", userID, actionID, oneMinuteFromNow},
|
||||
{"Bad userID", key, "foobar", actionID, oneMinuteFromNow},
|
||||
{"Bad actionID", key, userID, "foobar", oneMinuteFromNow},
|
||||
{"Expired", key, userID, actionID, now.Add(Timeout)},
|
||||
{"More than 1 minute from the future", key, userID, actionID, now.Add(-1*time.Nanosecond - 1*time.Minute)},
|
||||
}
|
||||
|
||||
tok := generateTokenAtTime(key, userID, actionID, now)
|
||||
for _, itt := range invalidTokenTests {
|
||||
assert.False(t, validTokenAtTime(tok, itt.key, itt.userID, itt.actionID, itt.t))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ValidateBadData primarily tests that no unexpected panics are triggered during parsing
|
||||
func Test_ValidateBadData(t *testing.T) {
|
||||
t.Run("Validate bad data", func(t *testing.T) {
|
||||
badDataTests := []struct {
|
||||
name, tok string
|
||||
}{
|
||||
{"Invalid Base64", "ASDab24(@)$*=="},
|
||||
{"No delimiter", base64.URLEncoding.EncodeToString([]byte("foobar12345678"))},
|
||||
{"Invalid time", base64.URLEncoding.EncodeToString([]byte("foobar:foobar"))},
|
||||
}
|
||||
|
||||
for _, bdt := range badDataTests {
|
||||
assert.False(t, validTokenAtTime(bdt.tok, key, userID, actionID, oneMinuteFromNow))
|
||||
}
|
||||
})
|
||||
}
|
|
@ -2,11 +2,15 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
package forms
|
||||
|
||||
import (
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/macaron/macaron"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
|
||||
// AdminCreateUserForm form for admin to create user
|
||||
|
@ -21,8 +25,9 @@ type AdminCreateUserForm struct {
|
|||
}
|
||||
|
||||
// Validate validates form fields
|
||||
func (f *AdminCreateUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AdminCreateUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// AdminEditUserForm form for admin to create user
|
||||
|
@ -47,8 +52,9 @@ type AdminEditUserForm struct {
|
|||
}
|
||||
|
||||
// Validate validates form fields
|
||||
func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AdminEditUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// AdminDashboardForm form for admin dashboard operations
|
||||
|
@ -58,6 +64,7 @@ type AdminDashboardForm struct {
|
|||
}
|
||||
|
||||
// Validate validates form fields
|
||||
func (f *AdminDashboardForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AdminDashboardForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
|
@ -2,11 +2,15 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
package forms
|
||||
|
||||
import (
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/macaron/macaron"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
|
||||
// AuthenticationForm form for authentication
|
||||
|
@ -65,6 +69,7 @@ type AuthenticationForm struct {
|
|||
}
|
||||
|
||||
// Validate validates fields
|
||||
func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AuthenticationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
|
@ -3,14 +3,17 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
package forms
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/macaron/macaron"
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
|
||||
// ________ .__ __ .__
|
||||
|
@ -28,8 +31,9 @@ type CreateOrgForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// UpdateOrgSettingForm form for updating organization settings
|
||||
|
@ -45,8 +49,9 @@ type UpdateOrgSettingForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ___________
|
||||
|
@ -67,6 +72,7 @@ type CreateTeamForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *CreateTeamForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *CreateTeamForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
|
@ -2,11 +2,15 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
package forms
|
||||
|
||||
import (
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/macaron/macaron"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
|
||||
// NewBranchForm form for creating a new branch
|
||||
|
@ -15,6 +19,7 @@ type NewBranchForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
|
@ -3,21 +3,23 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
package forms
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/utils"
|
||||
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/macaron/macaron"
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
|
||||
// _______________________________________ _________.______________________ _______________.___.
|
||||
|
@ -52,8 +54,9 @@ type CreateRepoForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *CreateRepoForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// MigrateRepoForm form for migrating repository
|
||||
|
@ -82,8 +85,9 @@ type MigrateRepoForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *MigrateRepoForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ParseRemoteAddr checks if given remote address is valid,
|
||||
|
@ -166,8 +170,9 @@ type RepoSettingForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *RepoSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __________ .__
|
||||
|
@ -202,8 +207,9 @@ type ProtectBranchForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *ProtectBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __ __ ___. .__ .__ __
|
||||
|
@ -263,8 +269,9 @@ type NewWebhookForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewWebhookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// NewGogshookForm form for creating gogs hook
|
||||
|
@ -276,8 +283,9 @@ type NewGogshookForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewGogshookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewGogshookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// NewSlackHookForm form for creating slack hook
|
||||
|
@ -291,8 +299,9 @@ type NewSlackHookForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// HasInvalidChannel validates the channel name is in the right format
|
||||
|
@ -309,8 +318,9 @@ type NewDiscordHookForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewDiscordHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewDiscordHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// NewDingtalkHookForm form for creating dingtalk hook
|
||||
|
@ -320,8 +330,9 @@ type NewDingtalkHookForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewDingtalkHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewDingtalkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// NewTelegramHookForm form for creating telegram hook
|
||||
|
@ -332,8 +343,9 @@ type NewTelegramHookForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewTelegramHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewTelegramHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// NewMatrixHookForm form for creating Matrix hook
|
||||
|
@ -346,8 +358,9 @@ type NewMatrixHookForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewMatrixHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewMatrixHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// NewMSTeamsHookForm form for creating MS Teams hook
|
||||
|
@ -357,8 +370,9 @@ type NewMSTeamsHookForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewMSTeamsHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewMSTeamsHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// NewFeishuHookForm form for creating feishu hook
|
||||
|
@ -368,8 +382,9 @@ type NewFeishuHookForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewFeishuHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewFeishuHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// .___
|
||||
|
@ -393,8 +408,9 @@ type CreateIssueForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *CreateIssueForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// CreateCommentForm form for creating comment
|
||||
|
@ -405,8 +421,9 @@ type CreateCommentForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *CreateCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *CreateCommentForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ReactionForm form for adding and removing reaction
|
||||
|
@ -415,8 +432,9 @@ type ReactionForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *ReactionForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *ReactionForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// IssueLockForm form for locking an issue
|
||||
|
@ -425,8 +443,9 @@ type IssueLockForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (i *IssueLockForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, i, ctx.Locale)
|
||||
func (i *IssueLockForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, i, ctx.Locale)
|
||||
}
|
||||
|
||||
// HasValidReason checks to make sure that the reason submitted in
|
||||
|
@ -489,8 +508,9 @@ type CreateMilestoneForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *CreateMilestoneForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// .____ ___. .__
|
||||
|
@ -509,8 +529,9 @@ type CreateLabelForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *CreateLabelForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// InitializeLabelsForm form for initializing labels
|
||||
|
@ -519,8 +540,9 @@ type InitializeLabelsForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *InitializeLabelsForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __________ .__ .__ __________ __
|
||||
|
@ -542,8 +564,9 @@ type MergePullRequestForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *MergePullRequestForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// CodeCommentForm form for adding code comments for PRs
|
||||
|
@ -559,8 +582,9 @@ type CodeCommentForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *CodeCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *CodeCommentForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// SubmitReviewForm for submitting a finished code review
|
||||
|
@ -571,8 +595,9 @@ type SubmitReviewForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *SubmitReviewForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *SubmitReviewForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ReviewType will return the corresponding reviewtype for type
|
||||
|
@ -616,8 +641,9 @@ type NewReleaseForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewReleaseForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// EditReleaseForm form for changing release
|
||||
|
@ -630,8 +656,9 @@ type EditReleaseForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *EditReleaseForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __ __.__ __ .__
|
||||
|
@ -650,8 +677,9 @@ type NewWikiForm struct {
|
|||
|
||||
// Validate validates the fields
|
||||
// FIXME: use code generation to generate this method.
|
||||
func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewWikiForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ___________ .___.__ __
|
||||
|
@ -673,8 +701,9 @@ type EditRepoFileForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *EditRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *EditRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// EditPreviewDiffForm form for changing preview diff
|
||||
|
@ -683,8 +712,9 @@ type EditPreviewDiffForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *EditPreviewDiffForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *EditPreviewDiffForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ____ ___ .__ .___
|
||||
|
@ -706,8 +736,9 @@ type UploadRepoFileForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *UploadRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *UploadRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// RemoveUploadFileForm form for removing uploaded file
|
||||
|
@ -716,8 +747,9 @@ type RemoveUploadFileForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *RemoveUploadFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *RemoveUploadFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ________ .__ __
|
||||
|
@ -737,8 +769,9 @@ type DeleteRepoFileForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *DeleteRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ___________.__ ___________ __
|
||||
|
@ -755,8 +788,9 @@ type AddTimeManuallyForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *AddTimeManuallyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AddTimeManuallyForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// SaveTopicForm form for save topics for repository
|
||||
|
@ -770,6 +804,7 @@ type DeadlineForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *DeadlineForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
package forms
|
||||
|
||||
import (
|
||||
"testing"
|
|
@ -3,16 +3,18 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
package forms
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/macaron/macaron"
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
|
||||
// InstallForm form for installation page
|
||||
|
@ -65,8 +67,9 @@ type InstallForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *InstallForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// _____ ____ _________________ ___
|
||||
|
@ -87,8 +90,9 @@ type RegisterForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// IsEmailDomainWhitelisted validates that the email address
|
||||
|
@ -124,8 +128,9 @@ type MustChangePasswordForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *MustChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *MustChangePasswordForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// SignInForm form for signing in with user/password
|
||||
|
@ -137,8 +142,9 @@ type SignInForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *SignInForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// AuthorizationForm form for authorizing oauth2 clients
|
||||
|
@ -156,8 +162,9 @@ type AuthorizationForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *AuthorizationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AuthorizationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// GrantApplicationForm form for authorizing oauth2 clients
|
||||
|
@ -170,8 +177,9 @@ type GrantApplicationForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *GrantApplicationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *GrantApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// AccessTokenForm for issuing access tokens from authorization codes or refresh tokens
|
||||
|
@ -188,8 +196,9 @@ type AccessTokenForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *AccessTokenForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AccessTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __________________________________________.___ _______ ________ _________
|
||||
|
@ -212,8 +221,9 @@ type UpdateProfileForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *UpdateProfileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// Avatar types
|
||||
|
@ -231,8 +241,9 @@ type AvatarForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *AvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AvatarForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// AddEmailForm form for adding new email
|
||||
|
@ -241,8 +252,9 @@ type AddEmailForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AddEmailForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// UpdateThemeForm form for updating a users' theme
|
||||
|
@ -251,8 +263,9 @@ type UpdateThemeForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the field
|
||||
func (f *UpdateThemeForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *UpdateThemeForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// IsThemeExists checks if the theme is a theme available in the config.
|
||||
|
@ -277,8 +290,9 @@ type ChangePasswordForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *ChangePasswordForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// AddOpenIDForm is for changing openid uri
|
||||
|
@ -287,8 +301,9 @@ type AddOpenIDForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *AddOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AddOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// AddKeyForm form for adding SSH/GPG key
|
||||
|
@ -300,8 +315,9 @@ type AddKeyForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *AddKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *AddKeyForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// NewAccessTokenForm form for creating access token
|
||||
|
@ -310,8 +326,9 @@ type NewAccessTokenForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *NewAccessTokenForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// EditOAuth2ApplicationForm form for editing oauth2 applications
|
||||
|
@ -321,8 +338,9 @@ type EditOAuth2ApplicationForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *EditOAuth2ApplicationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *EditOAuth2ApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// TwoFactorAuthForm for logging in with 2FA token.
|
||||
|
@ -331,8 +349,9 @@ type TwoFactorAuthForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *TwoFactorAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *TwoFactorAuthForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// TwoFactorScratchAuthForm for logging in with 2FA scratch token.
|
||||
|
@ -341,8 +360,9 @@ type TwoFactorScratchAuthForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *TwoFactorScratchAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *TwoFactorScratchAuthForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// U2FRegistrationForm for reserving an U2F name
|
||||
|
@ -351,8 +371,9 @@ type U2FRegistrationForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *U2FRegistrationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *U2FRegistrationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// U2FDeleteForm for deleting U2F keys
|
||||
|
@ -361,6 +382,7 @@ type U2FDeleteForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *U2FDeleteForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *U2FDeleteForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
|
@ -2,11 +2,14 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
package forms
|
||||
|
||||
import (
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/macaron/macaron"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
|
||||
// SignInOpenIDForm form for signing in with OpenID
|
||||
|
@ -16,8 +19,9 @@ type SignInOpenIDForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *SignInOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *SignInOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// SignUpOpenIDForm form for signin up with OpenID
|
||||
|
@ -29,8 +33,9 @@ type SignUpOpenIDForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *SignUpOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *SignUpOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ConnectOpenIDForm form for connecting an existing account to an OpenID URI
|
||||
|
@ -40,6 +45,7 @@ type ConnectOpenIDForm struct {
|
|||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *ConnectOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
func (f *ConnectOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetContext(req)
|
||||
return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
package forms
|
||||
|
||||
import (
|
||||
"testing"
|
|
@ -182,7 +182,7 @@ func PostLockHandler(ctx *context.Context) {
|
|||
}
|
||||
|
||||
var req api.LFSLockRequest
|
||||
bodyReader := ctx.Req.Body().ReadCloser()
|
||||
bodyReader := ctx.Req.Body
|
||||
defer bodyReader.Close()
|
||||
dec := json.NewDecoder(bodyReader)
|
||||
if err := dec.Decode(&req); err != nil {
|
||||
|
@ -317,7 +317,7 @@ func UnLockHandler(ctx *context.Context) {
|
|||
}
|
||||
|
||||
var req api.LFSLockDeleteRequest
|
||||
bodyReader := ctx.Req.Body().ReadCloser()
|
||||
bodyReader := ctx.Req.Body
|
||||
defer bodyReader.Close()
|
||||
dec := json.NewDecoder(bodyReader)
|
||||
if err := dec.Decode(&req); err != nil {
|
||||
|
|
|
@ -22,7 +22,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
||||
"gitea.com/macaron/macaron"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
|
@ -413,8 +412,8 @@ func PutHandler(ctx *context.Context) {
|
|||
}
|
||||
|
||||
contentStore := &ContentStore{ObjectStorage: storage.LFS}
|
||||
defer ctx.Req.Request.Body.Close()
|
||||
if err := contentStore.Put(meta, ctx.Req.Request.Body); err != nil {
|
||||
defer ctx.Req.Body.Close()
|
||||
if err := contentStore.Put(meta, ctx.Req.Body); err != nil {
|
||||
// Put will log the error itself
|
||||
ctx.Resp.WriteHeader(500)
|
||||
if err == errSizeMismatch || err == errHashMismatch {
|
||||
|
@ -513,7 +512,7 @@ func Represent(rv *RequestVars, meta *models.LFSMetaObject, download, upload boo
|
|||
|
||||
// MetaMatcher provides a mux.MatcherFunc that only allows requests that contain
|
||||
// an Accept header with the metaMediaType
|
||||
func MetaMatcher(r macaron.Request) bool {
|
||||
func MetaMatcher(r *http.Request) bool {
|
||||
mediaParts := strings.Split(r.Header.Get("Accept"), ";")
|
||||
mt := mediaParts[0]
|
||||
return mt == metaMediaType
|
||||
|
@ -530,7 +529,7 @@ func unpack(ctx *context.Context) *RequestVars {
|
|||
|
||||
if r.Method == "POST" { // Maybe also check if +json
|
||||
var p RequestVars
|
||||
bodyReader := r.Body().ReadCloser()
|
||||
bodyReader := r.Body
|
||||
defer bodyReader.Close()
|
||||
dec := json.NewDecoder(bodyReader)
|
||||
err := dec.Decode(&p)
|
||||
|
@ -553,7 +552,7 @@ func unpackbatch(ctx *context.Context) *BatchVars {
|
|||
r := ctx.Req
|
||||
var bv BatchVars
|
||||
|
||||
bodyReader := r.Body().ReadCloser()
|
||||
bodyReader := r.Body
|
||||
defer bodyReader.Close()
|
||||
dec := json.NewDecoder(bodyReader)
|
||||
err := dec.Decode(&bv)
|
||||
|
@ -586,7 +585,7 @@ func writeStatus(ctx *context.Context, status int) {
|
|||
logRequest(ctx.Req, status)
|
||||
}
|
||||
|
||||
func logRequest(r macaron.Request, status int) {
|
||||
func logRequest(r *http.Request, status int) {
|
||||
log.Debug("LFS request - Method: %s, URL: %s, Status %d", r.Method, r.URL, status)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,24 +3,19 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/macaron/macaron"
|
||||
"gitea.com/go-chi/binding"
|
||||
"github.com/unknwon/com"
|
||||
)
|
||||
|
||||
// IsAPIPath if URL is an api path
|
||||
func IsAPIPath(url string) bool {
|
||||
return strings.HasPrefix(url, "/api/")
|
||||
}
|
||||
|
||||
// Form form binding interface
|
||||
type Form interface {
|
||||
binding.Validator
|
||||
|
@ -35,7 +30,7 @@ func AssignForm(form interface{}, data map[string]interface{}) {
|
|||
typ := reflect.TypeOf(form)
|
||||
val := reflect.ValueOf(form)
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
for typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
val = val.Elem()
|
||||
}
|
||||
|
@ -84,7 +79,8 @@ func GetInclude(field reflect.StructField) string {
|
|||
return getRuleBody(field, "Include(")
|
||||
}
|
||||
|
||||
func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaron.Locale) binding.Errors {
|
||||
// Validate validate TODO:
|
||||
func Validate(errs binding.Errors, data map[string]interface{}, f Form, l translation.Locale) binding.Errors {
|
||||
if errs.Len() == 0 {
|
||||
return errs
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
// Copyright 2020 The Macaron Authors
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
@ -12,6 +13,56 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// MaxAge sets the maximum age for a provided cookie
|
||||
func MaxAge(maxAge int) func(*http.Cookie) {
|
||||
return func(c *http.Cookie) {
|
||||
c.MaxAge = maxAge
|
||||
}
|
||||
}
|
||||
|
||||
// Path sets the path for a provided cookie
|
||||
func Path(path string) func(*http.Cookie) {
|
||||
return func(c *http.Cookie) {
|
||||
c.Path = path
|
||||
}
|
||||
}
|
||||
|
||||
// Domain sets the domain for a provided cookie
|
||||
func Domain(domain string) func(*http.Cookie) {
|
||||
return func(c *http.Cookie) {
|
||||
c.Domain = domain
|
||||
}
|
||||
}
|
||||
|
||||
// Secure sets the secure setting for a provided cookie
|
||||
func Secure(secure bool) func(*http.Cookie) {
|
||||
return func(c *http.Cookie) {
|
||||
c.Secure = secure
|
||||
}
|
||||
}
|
||||
|
||||
// HTTPOnly sets the HttpOnly setting for a provided cookie
|
||||
func HTTPOnly(httpOnly bool) func(*http.Cookie) {
|
||||
return func(c *http.Cookie) {
|
||||
c.HttpOnly = httpOnly
|
||||
}
|
||||
}
|
||||
|
||||
// Expires sets the expires and rawexpires for a provided cookie
|
||||
func Expires(expires time.Time) func(*http.Cookie) {
|
||||
return func(c *http.Cookie) {
|
||||
c.Expires = expires
|
||||
c.RawExpires = expires.Format(time.UnixDate)
|
||||
}
|
||||
}
|
||||
|
||||
// SameSite sets the SameSite for a provided cookie
|
||||
func SameSite(sameSite http.SameSite) func(*http.Cookie) {
|
||||
return func(c *http.Cookie) {
|
||||
c.SameSite = sameSite
|
||||
}
|
||||
}
|
||||
|
||||
// NewCookie creates a cookie
|
||||
func NewCookie(name, value string, maxAge int) *http.Cookie {
|
||||
return &http.Cookie{
|
||||
|
@ -102,3 +153,13 @@ func SetCookie(resp http.ResponseWriter, name string, value string, others ...in
|
|||
|
||||
resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
|
||||
// GetCookie returns given cookie value from request header.
|
||||
func GetCookie(req *http.Request, name string) string {
|
||||
cookie, err := req.Cookie(name)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
val, _ := url.QueryUnescape(cookie.Value)
|
||||
return val
|
||||
}
|
||||
|
|
10
modules/middlewares/data.go
Normal file
10
modules/middlewares/data.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package middlewares
|
||||
|
||||
// DataStore represents a data store
|
||||
type DataStore interface {
|
||||
GetData() map[string]interface{}
|
||||
}
|
65
modules/middlewares/flash.go
Normal file
65
modules/middlewares/flash.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package middlewares
|
||||
|
||||
import "net/url"
|
||||
|
||||
// flashes enumerates all the flash types
|
||||
const (
|
||||
SuccessFlash = "SuccessMsg"
|
||||
ErrorFlash = "ErrorMsg"
|
||||
WarnFlash = "WarningMsg"
|
||||
InfoFlash = "InfoMsg"
|
||||
)
|
||||
|
||||
var (
|
||||
// FlashNow FIXME:
|
||||
FlashNow bool
|
||||
)
|
||||
|
||||
// Flash represents a one time data transfer between two requests.
|
||||
type Flash struct {
|
||||
DataStore
|
||||
url.Values
|
||||
ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
|
||||
}
|
||||
|
||||
func (f *Flash) set(name, msg string, current ...bool) {
|
||||
isShow := false
|
||||
if (len(current) == 0 && FlashNow) ||
|
||||
(len(current) > 0 && current[0]) {
|
||||
isShow = true
|
||||
}
|
||||
|
||||
if isShow {
|
||||
f.GetData()["Flash"] = f
|
||||
} else {
|
||||
f.Set(name, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Error sets error message
|
||||
func (f *Flash) Error(msg string, current ...bool) {
|
||||
f.ErrorMsg = msg
|
||||
f.set("error", msg, current...)
|
||||
}
|
||||
|
||||
// Warning sets warning message
|
||||
func (f *Flash) Warning(msg string, current ...bool) {
|
||||
f.WarningMsg = msg
|
||||
f.set("warning", msg, current...)
|
||||
}
|
||||
|
||||
// Info sets info message
|
||||
func (f *Flash) Info(msg string, current ...bool) {
|
||||
f.InfoMsg = msg
|
||||
f.set("info", msg, current...)
|
||||
}
|
||||
|
||||
// Success sets success message
|
||||
func (f *Flash) Success(msg string, current ...bool) {
|
||||
f.SuccessMsg = msg
|
||||
f.set("success", msg, current...)
|
||||
}
|
|
@ -23,12 +23,14 @@ func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale {
|
|||
// 2. Get language information from cookies.
|
||||
if len(lang) == 0 {
|
||||
ck, _ := req.Cookie("lang")
|
||||
lang = ck.Value
|
||||
hasCookie = true
|
||||
if ck != nil {
|
||||
lang = ck.Value
|
||||
hasCookie = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check again in case someone modify by purpose.
|
||||
if !i18n.IsExist(lang) {
|
||||
if lang != "" && !i18n.IsExist(lang) {
|
||||
lang = ""
|
||||
hasCookie = false
|
||||
}
|
||||
|
|
|
@ -1,217 +0,0 @@
|
|||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/nosql"
|
||||
|
||||
"gitea.com/go-chi/session"
|
||||
"github.com/go-redis/redis/v7"
|
||||
)
|
||||
|
||||
// RedisStore represents a redis session store implementation.
|
||||
// TODO: copied from modules/session/redis.go and should remove that one until macaron removed.
|
||||
type RedisStore struct {
|
||||
c redis.UniversalClient
|
||||
prefix, sid string
|
||||
duration time.Duration
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewRedisStore creates and returns a redis session store.
|
||||
func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore {
|
||||
return &RedisStore{
|
||||
c: c,
|
||||
prefix: prefix,
|
||||
sid: sid,
|
||||
duration: dur,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *RedisStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *RedisStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *RedisStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *RedisStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *RedisStore) Release() error {
|
||||
// Skip encoding if the data is empty
|
||||
if len(s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.c.Set(s.prefix+s.sid, string(data), s.duration).Err()
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *RedisStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// RedisProvider represents a redis session provider implementation.
|
||||
type RedisProvider struct {
|
||||
c redis.UniversalClient
|
||||
duration time.Duration
|
||||
prefix string
|
||||
}
|
||||
|
||||
// Init initializes redis session provider.
|
||||
// configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,prefix=session;
|
||||
func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) {
|
||||
p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uri := nosql.ToRedisURI(configs)
|
||||
|
||||
for k, v := range uri.Query() {
|
||||
switch k {
|
||||
case "prefix":
|
||||
p.prefix = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
p.c = nosql.GetManager().GetRedisClient(uri.String())
|
||||
return p.c.Ping().Err()
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
|
||||
psid := p.prefix + sid
|
||||
if !p.Exist(sid) {
|
||||
if err := p.c.Set(psid, "", p.duration).Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
kvs, err := p.c.Get(psid).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob([]byte(kvs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *RedisProvider) Exist(sid string) bool {
|
||||
v, err := p.c.Exists(p.prefix + sid).Result()
|
||||
return err == nil && v == 1
|
||||
}
|
||||
|
||||
// Destroy deletes a session by session ID.
|
||||
func (p *RedisProvider) Destroy(sid string) error {
|
||||
return p.c.Del(p.prefix + sid).Err()
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
poldsid := p.prefix + oldsid
|
||||
psid := p.prefix + sid
|
||||
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
} else if !p.Exist(oldsid) {
|
||||
// Make a fake old session.
|
||||
if err = p.c.Set(poldsid, "", p.duration).Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = p.c.Rename(poldsid, psid).Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
kvs, err := p.c.Get(psid).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob([]byte(kvs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *RedisProvider) Count() int {
|
||||
return int(p.c.DBSize().Val())
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (*RedisProvider) GC() {}
|
||||
|
||||
func init() {
|
||||
session.Register("redis", &RedisProvider{})
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"gitea.com/go-chi/session"
|
||||
couchbase "gitea.com/go-chi/session/couchbase"
|
||||
memcache "gitea.com/go-chi/session/memcache"
|
||||
mysql "gitea.com/go-chi/session/mysql"
|
||||
postgres "gitea.com/go-chi/session/postgres"
|
||||
)
|
||||
|
||||
// VirtualSessionProvider represents a shadowed session provider implementation.
|
||||
// TODO: copied from modules/session/redis.go and should remove that one until macaron removed.
|
||||
type VirtualSessionProvider struct {
|
||||
lock sync.RWMutex
|
||||
provider session.Provider
|
||||
}
|
||||
|
||||
// Init initializes the cookie session provider with given root path.
|
||||
func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
|
||||
var opts session.Options
|
||||
if err := json.Unmarshal([]byte(config), &opts); err != nil {
|
||||
return err
|
||||
}
|
||||
// Note that these options are unprepared so we can't just use NewManager here.
|
||||
// Nor can we access the provider map in session.
|
||||
// So we will just have to do this by hand.
|
||||
// This is only slightly more wrong than modules/setting/session.go:23
|
||||
switch opts.Provider {
|
||||
case "memory":
|
||||
o.provider = &session.MemProvider{}
|
||||
case "file":
|
||||
o.provider = &session.FileProvider{}
|
||||
case "redis":
|
||||
o.provider = &RedisProvider{}
|
||||
case "mysql":
|
||||
o.provider = &mysql.MysqlProvider{}
|
||||
case "postgres":
|
||||
o.provider = &postgres.PostgresProvider{}
|
||||
case "couchbase":
|
||||
o.provider = &couchbase.CouchbaseProvider{}
|
||||
case "memcache":
|
||||
o.provider = &memcache.MemcacheProvider{}
|
||||
default:
|
||||
return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
|
||||
}
|
||||
return o.provider.Init(gclifetime, opts.ProviderConfig)
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {
|
||||
o.lock.RLock()
|
||||
defer o.lock.RUnlock()
|
||||
if o.provider.Exist(sid) {
|
||||
return o.provider.Read(sid)
|
||||
}
|
||||
kv := make(map[interface{}]interface{})
|
||||
kv["_old_uid"] = "0"
|
||||
return NewVirtualStore(o, sid, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (o *VirtualSessionProvider) Exist(sid string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Destroy deletes a session by session ID.
|
||||
func (o *VirtualSessionProvider) Destroy(sid string) error {
|
||||
o.lock.Lock()
|
||||
defer o.lock.Unlock()
|
||||
return o.provider.Destroy(sid)
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (o *VirtualSessionProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
|
||||
o.lock.Lock()
|
||||
defer o.lock.Unlock()
|
||||
return o.provider.Regenerate(oldsid, sid)
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (o *VirtualSessionProvider) Count() int {
|
||||
o.lock.RLock()
|
||||
defer o.lock.RUnlock()
|
||||
return o.provider.Count()
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (o *VirtualSessionProvider) GC() {
|
||||
o.provider.GC()
|
||||
}
|
||||
|
||||
func init() {
|
||||
session.Register("VirtualSession", &VirtualSessionProvider{})
|
||||
}
|
||||
|
||||
// VirtualStore represents a virtual session store implementation.
|
||||
type VirtualStore struct {
|
||||
p *VirtualSessionProvider
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
released bool
|
||||
}
|
||||
|
||||
// NewVirtualStore creates and returns a virtual session store.
|
||||
func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[interface{}]interface{}) *VirtualStore {
|
||||
return &VirtualStore{
|
||||
p: p,
|
||||
sid: sid,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *VirtualStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *VirtualStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *VirtualStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *VirtualStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *VirtualStore) Release() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
// Now need to lock the provider
|
||||
s.p.lock.Lock()
|
||||
defer s.p.lock.Unlock()
|
||||
if oldUID, ok := s.data["_old_uid"]; (ok && (oldUID != "0" || len(s.data) > 1)) || (!ok && len(s.data) > 0) {
|
||||
// Now ensure that we don't exist!
|
||||
realProvider := s.p.provider
|
||||
|
||||
if !s.released && realProvider.Exist(s.sid) {
|
||||
// This is an error!
|
||||
return fmt.Errorf("new sid '%s' already exists", s.sid)
|
||||
}
|
||||
realStore, err := realProvider.Read(s.sid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := realStore.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range s.data {
|
||||
if err := realStore.Set(key, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = realStore.Release()
|
||||
if err == nil {
|
||||
s.released = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *VirtualStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
|
@ -23,7 +23,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/nosql"
|
||||
|
||||
"gitea.com/macaron/session"
|
||||
"gitea.com/go-chi/session"
|
||||
"github.com/go-redis/redis/v7"
|
||||
)
|
||||
|
||||
|
|
12
modules/session/store.go
Normal file
12
modules/session/store.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package session
|
||||
|
||||
// Store represents a session store
|
||||
type Store interface {
|
||||
Get(interface{}) interface{}
|
||||
Set(interface{}, interface{}) error
|
||||
Delete(interface{}) error
|
||||
}
|
|
@ -9,12 +9,11 @@ import (
|
|||
"fmt"
|
||||
"sync"
|
||||
|
||||
"gitea.com/macaron/session"
|
||||
couchbase "gitea.com/macaron/session/couchbase"
|
||||
memcache "gitea.com/macaron/session/memcache"
|
||||
mysql "gitea.com/macaron/session/mysql"
|
||||
nodb "gitea.com/macaron/session/nodb"
|
||||
postgres "gitea.com/macaron/session/postgres"
|
||||
"gitea.com/go-chi/session"
|
||||
couchbase "gitea.com/go-chi/session/couchbase"
|
||||
memcache "gitea.com/go-chi/session/memcache"
|
||||
mysql "gitea.com/go-chi/session/mysql"
|
||||
postgres "gitea.com/go-chi/session/postgres"
|
||||
)
|
||||
|
||||
// VirtualSessionProvider represents a shadowed session provider implementation.
|
||||
|
@ -48,8 +47,6 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
|
|||
o.provider = &couchbase.CouchbaseProvider{}
|
||||
case "memcache":
|
||||
o.provider = &memcache.MemcacheProvider{}
|
||||
case "nodb":
|
||||
o.provider = &nodb.NodbProvider{}
|
||||
default:
|
||||
return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
|
||||
}
|
||||
|
|
|
@ -254,17 +254,6 @@ func generateNamedLogger(key string, options defaultLogOptions) *LogDescription
|
|||
return &description
|
||||
}
|
||||
|
||||
func newMacaronLogService() {
|
||||
options := newDefaultLogOptions()
|
||||
options.filename = filepath.Join(LogRootPath, "macaron.log")
|
||||
options.bufferLength = Cfg.Section("log").Key("BUFFER_LEN").MustInt64(10000)
|
||||
|
||||
Cfg.Section("log").Key("MACARON").MustString("file")
|
||||
if RedirectMacaronLog {
|
||||
generateNamedLogger("macaron", options)
|
||||
}
|
||||
}
|
||||
|
||||
func newAccessLogService() {
|
||||
EnableAccessLog = Cfg.Section("log").Key("ENABLE_ACCESS_LOG").MustBool(false)
|
||||
AccessLogTemplate = Cfg.Section("log").Key("ACCESS_LOG_TEMPLATE").MustString(
|
||||
|
@ -360,7 +349,6 @@ func RestartLogsWithPIDSuffix() {
|
|||
// NewLogServices creates all the log services
|
||||
func NewLogServices(disableConsole bool) {
|
||||
newLogService()
|
||||
newMacaronLogService()
|
||||
newRouterLogService()
|
||||
newAccessLogService()
|
||||
NewXORMLogService(disableConsole)
|
||||
|
|
|
@ -41,7 +41,7 @@ var (
|
|||
func newSessionService() {
|
||||
sec := Cfg.Section("session")
|
||||
SessionConfig.Provider = sec.Key("PROVIDER").In("memory",
|
||||
[]string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache", "nodb"})
|
||||
[]string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache"})
|
||||
SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ")
|
||||
if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
|
||||
SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/unrolled/render"
|
||||
)
|
||||
|
||||
// Vars represents variables to be render in golang templates
|
||||
|
@ -80,3 +82,15 @@ func getDirAssetNames(dir string) []string {
|
|||
}
|
||||
return tmpls
|
||||
}
|
||||
|
||||
// HTMLRenderer returns a render.
|
||||
func HTMLRenderer() *render.Render {
|
||||
return render.New(render.Options{
|
||||
Extensions: []string{".tmpl"},
|
||||
Directory: "templates",
|
||||
Funcs: NewFuncMap(),
|
||||
Asset: GetAsset,
|
||||
AssetNames: GetAssetNames,
|
||||
IsDevelopment: !setting.IsProd(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,8 +18,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"gitea.com/macaron/macaron"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -46,29 +44,6 @@ func GetAssetNames() []string {
|
|||
return append(tmpls, tmpls2...)
|
||||
}
|
||||
|
||||
// HTMLRenderer implements the macaron handler for serving HTML templates.
|
||||
func HTMLRenderer() macaron.Handler {
|
||||
return macaron.Renderer(macaron.RenderOptions{
|
||||
Funcs: NewFuncMap(),
|
||||
Directory: path.Join(setting.StaticRootPath, "templates"),
|
||||
AppendDirectories: []string{
|
||||
path.Join(setting.CustomPath, "templates"),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// JSONRenderer implements the macaron handler for serving JSON templates.
|
||||
func JSONRenderer() macaron.Handler {
|
||||
return macaron.Renderer(macaron.RenderOptions{
|
||||
Funcs: NewFuncMap(),
|
||||
Directory: path.Join(setting.StaticRootPath, "templates"),
|
||||
AppendDirectories: []string{
|
||||
path.Join(setting.CustomPath, "templates"),
|
||||
},
|
||||
HTMLContentType: "application/json",
|
||||
})
|
||||
}
|
||||
|
||||
// Mailer provides the templates required for sending notification mails.
|
||||
func Mailer() (*texttmpl.Template, *template.Template) {
|
||||
for _, funcs := range NewTextFuncMap() {
|
||||
|
|
|
@ -7,10 +7,7 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -21,8 +18,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"gitea.com/macaron/macaron"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -30,24 +25,6 @@ var (
|
|||
bodyTemplates = template.New("")
|
||||
)
|
||||
|
||||
type templateFileSystem struct {
|
||||
files []macaron.TemplateFile
|
||||
}
|
||||
|
||||
func (templates templateFileSystem) ListFiles() []macaron.TemplateFile {
|
||||
return templates.files
|
||||
}
|
||||
|
||||
func (templates templateFileSystem) Get(name string) (io.Reader, error) {
|
||||
for i := range templates.files {
|
||||
if templates.files[i].Name()+templates.files[i].Ext() == name {
|
||||
return bytes.NewReader(templates.files[i].Data()), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("file '%s' not found", name)
|
||||
}
|
||||
|
||||
// GetAsset get a special asset, only for chi
|
||||
func GetAsset(name string) ([]byte, error) {
|
||||
bs, err := ioutil.ReadFile(filepath.Join(setting.CustomPath, name))
|
||||
|
@ -72,95 +49,6 @@ func GetAssetNames() []string {
|
|||
return append(tmpls, customTmpls...)
|
||||
}
|
||||
|
||||
func NewTemplateFileSystem() templateFileSystem {
|
||||
fs := templateFileSystem{}
|
||||
fs.files = make([]macaron.TemplateFile, 0, 10)
|
||||
|
||||
for _, assetPath := range AssetNames() {
|
||||
if strings.HasPrefix(assetPath, "mail/") {
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(assetPath, ".tmpl") {
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := Asset(assetPath)
|
||||
|
||||
if err != nil {
|
||||
log.Warn("Failed to read embedded %s template. %v", assetPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
fs.files = append(fs.files, macaron.NewTplFile(
|
||||
strings.TrimSuffix(
|
||||
assetPath,
|
||||
".tmpl",
|
||||
),
|
||||
content,
|
||||
".tmpl",
|
||||
))
|
||||
}
|
||||
|
||||
customDir := path.Join(setting.CustomPath, "templates")
|
||||
isDir, err := util.IsDir(customDir)
|
||||
if err != nil {
|
||||
log.Warn("Unable to check if templates dir %s is a directory. Error: %v", customDir, err)
|
||||
}
|
||||
if isDir {
|
||||
files, err := util.StatDir(customDir)
|
||||
|
||||
if err != nil {
|
||||
log.Warn("Failed to read %s templates dir. %v", customDir, err)
|
||||
} else {
|
||||
for _, filePath := range files {
|
||||
if strings.HasPrefix(filePath, "mail/") {
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(filePath, ".tmpl") {
|
||||
continue
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path.Join(customDir, filePath))
|
||||
|
||||
if err != nil {
|
||||
log.Warn("Failed to read custom %s template. %v", filePath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
fs.files = append(fs.files, macaron.NewTplFile(
|
||||
strings.TrimSuffix(
|
||||
filePath,
|
||||
".tmpl",
|
||||
),
|
||||
content,
|
||||
".tmpl",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fs
|
||||
}
|
||||
|
||||
// HTMLRenderer implements the macaron handler for serving HTML templates.
|
||||
func HTMLRenderer() macaron.Handler {
|
||||
return macaron.Renderer(macaron.RenderOptions{
|
||||
Funcs: NewFuncMap(),
|
||||
TemplateFileSystem: NewTemplateFileSystem(),
|
||||
})
|
||||
}
|
||||
|
||||
// JSONRenderer implements the macaron handler for serving JSON templates.
|
||||
func JSONRenderer() macaron.Handler {
|
||||
return macaron.Renderer(macaron.RenderOptions{
|
||||
Funcs: NewFuncMap(),
|
||||
TemplateFileSystem: NewTemplateFileSystem(),
|
||||
HTMLContentType: "application/json",
|
||||
})
|
||||
}
|
||||
|
||||
// Mailer provides the templates required for sending notification mails.
|
||||
func Mailer() (*texttmpl.Template, *template.Template) {
|
||||
for _, funcs := range NewTextFuncMap() {
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
scontext "context"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
@ -13,32 +16,37 @@ import (
|
|||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
|
||||
"gitea.com/macaron/macaron"
|
||||
"gitea.com/macaron/session"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/unrolled/render"
|
||||
)
|
||||
|
||||
// MockContext mock context for unit tests
|
||||
func MockContext(t *testing.T, path string) *context.Context {
|
||||
var macaronContext macaron.Context
|
||||
macaronContext.ReplaceAllParams(macaron.Params{})
|
||||
macaronContext.Locale = &mockLocale{}
|
||||
requestURL, err := url.Parse(path)
|
||||
assert.NoError(t, err)
|
||||
macaronContext.Req = macaron.Request{Request: &http.Request{
|
||||
URL: requestURL,
|
||||
Form: url.Values{},
|
||||
}}
|
||||
macaronContext.Resp = &mockResponseWriter{}
|
||||
macaronContext.Render = &mockRender{ResponseWriter: macaronContext.Resp}
|
||||
macaronContext.Data = map[string]interface{}{}
|
||||
return &context.Context{
|
||||
Context: &macaronContext,
|
||||
Flash: &session.Flash{
|
||||
var resp = &mockResponseWriter{}
|
||||
var ctx = context.Context{
|
||||
Render: &mockRender{},
|
||||
Data: make(map[string]interface{}),
|
||||
Flash: &middlewares.Flash{
|
||||
Values: make(url.Values),
|
||||
},
|
||||
Resp: context.NewResponse(resp),
|
||||
Locale: &mockLocale{},
|
||||
}
|
||||
|
||||
requestURL, err := url.Parse(path)
|
||||
assert.NoError(t, err)
|
||||
var req = &http.Request{
|
||||
URL: requestURL,
|
||||
Form: url.Values{},
|
||||
}
|
||||
|
||||
chiCtx := chi.NewRouteContext()
|
||||
req = req.WithContext(scontext.WithValue(req.Context(), chi.RouteCtxKey, chiCtx))
|
||||
ctx.Req = context.WithContext(req, &ctx)
|
||||
return &ctx
|
||||
}
|
||||
|
||||
// LoadRepo load a repo into a test context.
|
||||
|
@ -113,77 +121,20 @@ func (rw *mockResponseWriter) Size() int {
|
|||
return rw.size
|
||||
}
|
||||
|
||||
func (rw *mockResponseWriter) Before(b macaron.BeforeFunc) {
|
||||
b(rw)
|
||||
}
|
||||
|
||||
func (rw *mockResponseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockRender struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (tr *mockRender) SetResponseWriter(rw http.ResponseWriter) {
|
||||
tr.ResponseWriter = rw
|
||||
func (tr *mockRender) TemplateLookup(tmpl string) *template.Template {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *mockRender) JSON(status int, _ interface{}) {
|
||||
tr.Status(status)
|
||||
}
|
||||
|
||||
func (tr *mockRender) JSONString(interface{}) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (tr *mockRender) RawData(status int, _ []byte) {
|
||||
tr.Status(status)
|
||||
}
|
||||
|
||||
func (tr *mockRender) PlainText(status int, _ []byte) {
|
||||
tr.Status(status)
|
||||
}
|
||||
|
||||
func (tr *mockRender) HTML(status int, _ string, _ interface{}, _ ...macaron.HTMLOptions) {
|
||||
tr.Status(status)
|
||||
}
|
||||
|
||||
func (tr *mockRender) HTMLSet(status int, _ string, _ string, _ interface{}, _ ...macaron.HTMLOptions) {
|
||||
tr.Status(status)
|
||||
}
|
||||
|
||||
func (tr *mockRender) HTMLSetString(string, string, interface{}, ...macaron.HTMLOptions) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (tr *mockRender) HTMLString(string, interface{}, ...macaron.HTMLOptions) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (tr *mockRender) HTMLSetBytes(string, string, interface{}, ...macaron.HTMLOptions) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (tr *mockRender) HTMLBytes(string, interface{}, ...macaron.HTMLOptions) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (tr *mockRender) XML(status int, _ interface{}) {
|
||||
tr.Status(status)
|
||||
}
|
||||
|
||||
func (tr *mockRender) Error(status int, _ ...string) {
|
||||
tr.Status(status)
|
||||
}
|
||||
|
||||
func (tr *mockRender) Status(status int) {
|
||||
tr.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (tr *mockRender) SetTemplatePath(string, string) {
|
||||
}
|
||||
|
||||
func (tr *mockRender) HasTemplateSet(string) bool {
|
||||
return true
|
||||
func (tr *mockRender) HTML(w io.Writer, status int, _ string, _ interface{}, _ ...render.HTMLOptions) error {
|
||||
if resp, ok := w.(http.ResponseWriter); ok {
|
||||
resp.WriteHeader(status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ import (
|
|||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
|
||||
macaroni18n "gitea.com/macaron/i18n"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/unknwon/i18n"
|
||||
)
|
||||
|
@ -27,13 +27,11 @@ const (
|
|||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setting.StaticRootPath = "../../"
|
||||
setting.Names = []string{"english"}
|
||||
setting.Langs = []string{"en-US"}
|
||||
// setup
|
||||
macaroni18n.I18n(macaroni18n.Options{
|
||||
Directory: "../../options/locale/",
|
||||
DefaultLang: "en-US",
|
||||
Langs: []string{"en-US"},
|
||||
Names: []string{"english"},
|
||||
})
|
||||
translation.InitLocales()
|
||||
BaseDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// run the tests
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/options"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
macaron_i18n "gitea.com/macaron/i18n"
|
||||
"github.com/unknwon/i18n"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
@ -20,49 +19,57 @@ type Locale interface {
|
|||
Tr(string, ...interface{}) string
|
||||
}
|
||||
|
||||
// LangType represents a lang type
|
||||
type LangType struct {
|
||||
Lang, Name string
|
||||
}
|
||||
|
||||
var (
|
||||
matcher language.Matcher
|
||||
matcher language.Matcher
|
||||
allLangs []LangType
|
||||
)
|
||||
|
||||
// AllLangs returns all supported langauages
|
||||
func AllLangs() []LangType {
|
||||
return allLangs
|
||||
}
|
||||
|
||||
// InitLocales loads the locales
|
||||
func InitLocales() {
|
||||
localeNames, err := options.Dir("locale")
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Failed to list locale files: %v", err)
|
||||
}
|
||||
localFiles := make(map[string][]byte)
|
||||
|
||||
localFiles := make(map[string][]byte)
|
||||
for _, name := range localeNames {
|
||||
localFiles[name], err = options.Locale(name)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load %s locale file. %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// These codes will be used once macaron removed
|
||||
/*tags := make([]language.Tag, len(setting.Langs))
|
||||
tags := make([]language.Tag, len(setting.Langs))
|
||||
for i, lang := range setting.Langs {
|
||||
tags[i] = language.Raw.Make(lang)
|
||||
}
|
||||
matcher = language.NewMatcher(tags)
|
||||
for i, name := range setting.Names {
|
||||
i18n.SetMessage(setting.Langs[i], localFiles[name])
|
||||
}
|
||||
i18n.SetDefaultLang("en-US")*/
|
||||
|
||||
// To be compatible with macaron, we now have to use macaron i18n, once macaron
|
||||
// removed, we can use i18n directly
|
||||
macaron_i18n.I18n(macaron_i18n.Options{
|
||||
SubURL: setting.AppSubURL,
|
||||
Files: localFiles,
|
||||
Langs: setting.Langs,
|
||||
Names: setting.Names,
|
||||
DefaultLang: "en-US",
|
||||
Redirect: false,
|
||||
CookieDomain: setting.SessionConfig.Domain,
|
||||
})
|
||||
matcher = language.NewMatcher(tags)
|
||||
for i := range setting.Names {
|
||||
key := "locale_" + setting.Langs[i] + ".ini"
|
||||
if err := i18n.SetMessageWithDesc(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil {
|
||||
log.Fatal("Failed to set messages to %s: %v", setting.Langs[i], err)
|
||||
}
|
||||
}
|
||||
i18n.SetDefaultLang("en-US")
|
||||
|
||||
allLangs = make([]LangType, 0, i18n.Count()-1)
|
||||
langs := i18n.ListLangs()
|
||||
names := i18n.ListLangDescs()
|
||||
for i, v := range langs {
|
||||
allLangs = append(allLangs, LangType{v, names[i]})
|
||||
}
|
||||
}
|
||||
|
||||
// Match matches accept languages
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/go-chi/binding"
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/macaron/macaron"
|
||||
"gitea.com/go-chi/binding"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -34,9 +34,10 @@ type (
|
|||
|
||||
func performValidationTest(t *testing.T, testCase validationTestCase) {
|
||||
httpRecorder := httptest.NewRecorder()
|
||||
m := macaron.Classic()
|
||||
m := chi.NewRouter()
|
||||
|
||||
m.Post(testRoute, binding.Validate(testCase.data), func(actual binding.Errors) {
|
||||
m.Post(testRoute, func(resp http.ResponseWriter, req *http.Request) {
|
||||
actual := binding.Validate(req, testCase.data)
|
||||
// see https://github.com/stretchr/testify/issues/435
|
||||
if actual == nil {
|
||||
actual = binding.Errors{}
|
||||
|
@ -49,7 +50,7 @@ func performValidationTest(t *testing.T, testCase validationTestCase) {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "x-www-form-urlencoded")
|
||||
m.ServeHTTP(httpRecorder, req)
|
||||
|
||||
switch httpRecorder.Code {
|
||||
|
|
|
@ -7,7 +7,7 @@ package validation
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/go-chi/binding"
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ package validation
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
|
||||
var gitRefNameValidationTestCases = []validationTestCase{
|
||||
|
|
|
@ -7,7 +7,7 @@ package validation
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.com/macaron/binding"
|
||||
"gitea.com/go-chi/binding"
|
||||
)
|
||||
|
||||
var urlValidationTestCases = []validationTestCase{
|
||||
|
|
322
modules/web/route.go
Normal file
322
modules/web/route.go
Normal file
|
@ -0,0 +1,322 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/middlewares"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
// Wrap converts all kinds of routes to standard library one
|
||||
func Wrap(handlers ...interface{}) http.HandlerFunc {
|
||||
if len(handlers) == 0 {
|
||||
panic("No handlers found")
|
||||
}
|
||||
|
||||
for _, handler := range handlers {
|
||||
switch t := handler.(type) {
|
||||
case http.HandlerFunc, func(http.ResponseWriter, *http.Request),
|
||||
func(ctx *context.Context),
|
||||
func(*context.APIContext),
|
||||
func(*context.PrivateContext),
|
||||
func(http.Handler) http.Handler:
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported handler type: %#v", t))
|
||||
}
|
||||
}
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
for i := 0; i < len(handlers); i++ {
|
||||
handler := handlers[i]
|
||||
switch t := handler.(type) {
|
||||
case http.HandlerFunc:
|
||||
t(resp, req)
|
||||
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
|
||||
return
|
||||
}
|
||||
case func(http.ResponseWriter, *http.Request):
|
||||
t(resp, req)
|
||||
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
|
||||
return
|
||||
}
|
||||
case func(ctx *context.Context):
|
||||
ctx := context.GetContext(req)
|
||||
t(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
case func(*context.APIContext):
|
||||
ctx := context.GetAPIContext(req)
|
||||
t(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
case func(*context.PrivateContext):
|
||||
ctx := context.GetPrivateContext(req)
|
||||
t(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
case func(http.Handler) http.Handler:
|
||||
var next = http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})
|
||||
t(next).ServeHTTP(resp, req)
|
||||
if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
|
||||
return
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported handler type: %#v", t))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Middle wrap a context function as a chi middleware
|
||||
func Middle(f func(ctx *context.Context)) func(netx http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
ctx := context.GetContext(req)
|
||||
f(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MiddleAPI wrap a context function as a chi middleware
|
||||
func MiddleAPI(f func(ctx *context.APIContext)) func(netx http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
ctx := context.GetAPIContext(req)
|
||||
f(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Bind binding an obj to a handler
|
||||
func Bind(obj interface{}) http.HandlerFunc {
|
||||
var tp = reflect.TypeOf(obj)
|
||||
if tp.Kind() == reflect.Ptr {
|
||||
tp = tp.Elem()
|
||||
}
|
||||
if tp.Kind() != reflect.Struct {
|
||||
panic("Only structs are allowed to bind")
|
||||
}
|
||||
return Wrap(func(ctx *context.Context) {
|
||||
var theObj = reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly
|
||||
binding.Bind(ctx.Req, theObj)
|
||||
SetForm(ctx, theObj)
|
||||
middlewares.AssignForm(theObj, ctx.Data)
|
||||
})
|
||||
}
|
||||
|
||||
// SetForm set the form object
|
||||
func SetForm(data middlewares.DataStore, obj interface{}) {
|
||||
data.GetData()["__form"] = obj
|
||||
}
|
||||
|
||||
// GetForm returns the validate form information
|
||||
func GetForm(data middlewares.DataStore) interface{} {
|
||||
return data.GetData()["__form"]
|
||||
}
|
||||
|
||||
// Route defines a route based on chi's router
|
||||
type Route struct {
|
||||
R chi.Router
|
||||
curGroupPrefix string
|
||||
curMiddlewares []interface{}
|
||||
}
|
||||
|
||||
// NewRoute creates a new route
|
||||
func NewRoute() *Route {
|
||||
r := chi.NewRouter()
|
||||
return &Route{
|
||||
R: r,
|
||||
curGroupPrefix: "",
|
||||
curMiddlewares: []interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// Use supports two middlewares
|
||||
func (r *Route) Use(middlewares ...interface{}) {
|
||||
if r.curGroupPrefix != "" {
|
||||
r.curMiddlewares = append(r.curMiddlewares, middlewares...)
|
||||
} else {
|
||||
for _, middle := range middlewares {
|
||||
switch t := middle.(type) {
|
||||
case func(http.Handler) http.Handler:
|
||||
r.R.Use(t)
|
||||
case func(*context.Context):
|
||||
r.R.Use(Middle(t))
|
||||
case func(*context.APIContext):
|
||||
r.R.Use(MiddleAPI(t))
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported middleware type: %#v", t))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group mounts a sub-Router along a `pattern` string.
|
||||
func (r *Route) Group(pattern string, fn func(), middlewares ...interface{}) {
|
||||
var previousGroupPrefix = r.curGroupPrefix
|
||||
var previousMiddlewares = r.curMiddlewares
|
||||
r.curGroupPrefix += pattern
|
||||
r.curMiddlewares = append(r.curMiddlewares, middlewares...)
|
||||
|
||||
fn()
|
||||
|
||||
r.curGroupPrefix = previousGroupPrefix
|
||||
r.curMiddlewares = previousMiddlewares
|
||||
}
|
||||
|
||||
func (r *Route) getPattern(pattern string) string {
|
||||
newPattern := r.curGroupPrefix + pattern
|
||||
if !strings.HasPrefix(newPattern, "/") {
|
||||
newPattern = "/" + newPattern
|
||||
}
|
||||
if newPattern == "/" {
|
||||
return newPattern
|
||||
}
|
||||
return strings.TrimSuffix(newPattern, "/")
|
||||
}
|
||||
|
||||
// Mount attaches another Route along ./pattern/*
|
||||
func (r *Route) Mount(pattern string, subR *Route) {
|
||||
var middlewares = make([]interface{}, len(r.curMiddlewares))
|
||||
copy(middlewares, r.curMiddlewares)
|
||||
subR.Use(middlewares...)
|
||||
r.R.Mount(r.getPattern(pattern), subR.R)
|
||||
}
|
||||
|
||||
// Any delegate requests for all methods
|
||||
func (r *Route) Any(pattern string, h ...interface{}) {
|
||||
var middlewares = r.getMiddlewares(h)
|
||||
r.R.HandleFunc(r.getPattern(pattern), Wrap(middlewares...))
|
||||
}
|
||||
|
||||
// Route delegate special methods
|
||||
func (r *Route) Route(pattern string, methods string, h ...interface{}) {
|
||||
p := r.getPattern(pattern)
|
||||
ms := strings.Split(methods, ",")
|
||||
var middlewares = r.getMiddlewares(h)
|
||||
for _, method := range ms {
|
||||
r.R.MethodFunc(strings.TrimSpace(method), p, Wrap(middlewares...))
|
||||
}
|
||||
}
|
||||
|
||||
// Delete delegate delete method
|
||||
func (r *Route) Delete(pattern string, h ...interface{}) {
|
||||
var middlewares = r.getMiddlewares(h)
|
||||
r.R.Delete(r.getPattern(pattern), Wrap(middlewares...))
|
||||
}
|
||||
|
||||
func (r *Route) getMiddlewares(h []interface{}) []interface{} {
|
||||
var middlewares = make([]interface{}, len(r.curMiddlewares), len(r.curMiddlewares)+len(h))
|
||||
copy(middlewares, r.curMiddlewares)
|
||||
middlewares = append(middlewares, h...)
|
||||
return middlewares
|
||||
}
|
||||
|
||||
// Get delegate get method
|
||||
func (r *Route) Get(pattern string, h ...interface{}) {
|
||||
var middlewares = r.getMiddlewares(h)
|
||||
r.R.Get(r.getPattern(pattern), Wrap(middlewares...))
|
||||
}
|
||||
|
||||
// Head delegate head method
|
||||
func (r *Route) Head(pattern string, h ...interface{}) {
|
||||
var middlewares = r.getMiddlewares(h)
|
||||
r.R.Head(r.getPattern(pattern), Wrap(middlewares...))
|
||||
}
|
||||
|
||||
// Post delegate post method
|
||||
func (r *Route) Post(pattern string, h ...interface{}) {
|
||||
var middlewares = r.getMiddlewares(h)
|
||||
r.R.Post(r.getPattern(pattern), Wrap(middlewares...))
|
||||
}
|
||||
|
||||
// Put delegate put method
|
||||
func (r *Route) Put(pattern string, h ...interface{}) {
|
||||
var middlewares = r.getMiddlewares(h)
|
||||
r.R.Put(r.getPattern(pattern), Wrap(middlewares...))
|
||||
}
|
||||
|
||||
// Patch delegate patch method
|
||||
func (r *Route) Patch(pattern string, h ...interface{}) {
|
||||
var middlewares = r.getMiddlewares(h)
|
||||
r.R.Patch(r.getPattern(pattern), Wrap(middlewares...))
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler
|
||||
func (r *Route) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
r.R.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
// NotFound defines a handler to respond whenever a route could
|
||||
// not be found.
|
||||
func (r *Route) NotFound(h http.HandlerFunc) {
|
||||
r.R.NotFound(h)
|
||||
}
|
||||
|
||||
// MethodNotAllowed defines a handler to respond whenever a method is
|
||||
// not allowed.
|
||||
func (r *Route) MethodNotAllowed(h http.HandlerFunc) {
|
||||
r.R.MethodNotAllowed(h)
|
||||
}
|
||||
|
||||
// Combo deletegate requests to Combo
|
||||
func (r *Route) Combo(pattern string, h ...interface{}) *Combo {
|
||||
return &Combo{r, pattern, h}
|
||||
}
|
||||
|
||||
// Combo represents a tiny group routes with same pattern
|
||||
type Combo struct {
|
||||
r *Route
|
||||
pattern string
|
||||
h []interface{}
|
||||
}
|
||||
|
||||
// Get deletegate Get method
|
||||
func (c *Combo) Get(h ...interface{}) *Combo {
|
||||
c.r.Get(c.pattern, append(c.h, h...)...)
|
||||
return c
|
||||
}
|
||||
|
||||
// Post deletegate Post method
|
||||
func (c *Combo) Post(h ...interface{}) *Combo {
|
||||
c.r.Post(c.pattern, append(c.h, h...)...)
|
||||
return c
|
||||
}
|
||||
|
||||
// Delete deletegate Delete method
|
||||
func (c *Combo) Delete(h ...interface{}) *Combo {
|
||||
c.r.Delete(c.pattern, append(c.h, h...)...)
|
||||
return c
|
||||
}
|
||||
|
||||
// Put deletegate Put method
|
||||
func (c *Combo) Put(h ...interface{}) *Combo {
|
||||
c.r.Put(c.pattern, append(c.h, h...)...)
|
||||
return c
|
||||
}
|
||||
|
||||
// Patch deletegate Patch method
|
||||
func (c *Combo) Patch(h ...interface{}) *Combo {
|
||||
c.r.Patch(c.pattern, append(c.h, h...)...)
|
||||
return c
|
||||
}
|
169
modules/web/route_test.go
Normal file
169
modules/web/route_test.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRoute1(t *testing.T) {
|
||||
buff := bytes.NewBufferString("")
|
||||
recorder := httptest.NewRecorder()
|
||||
recorder.Body = buff
|
||||
|
||||
r := NewRoute()
|
||||
r.Get("/{username}/{reponame}/{type:issues|pulls}", func(resp http.ResponseWriter, req *http.Request) {
|
||||
username := chi.URLParam(req, "username")
|
||||
assert.EqualValues(t, "gitea", username)
|
||||
reponame := chi.URLParam(req, "reponame")
|
||||
assert.EqualValues(t, "gitea", reponame)
|
||||
tp := chi.URLParam(req, "type")
|
||||
assert.EqualValues(t, "issues", tp)
|
||||
})
|
||||
|
||||
req, err := http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues", nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||
}
|
||||
|
||||
func TestRoute2(t *testing.T) {
|
||||
buff := bytes.NewBufferString("")
|
||||
recorder := httptest.NewRecorder()
|
||||
recorder.Body = buff
|
||||
|
||||
var route int
|
||||
|
||||
r := NewRoute()
|
||||
r.Group("/{username}/{reponame}", func() {
|
||||
r.Group("", func() {
|
||||
r.Get("/{type:issues|pulls}", func(resp http.ResponseWriter, req *http.Request) {
|
||||
username := chi.URLParam(req, "username")
|
||||
assert.EqualValues(t, "gitea", username)
|
||||
reponame := chi.URLParam(req, "reponame")
|
||||
assert.EqualValues(t, "gitea", reponame)
|
||||
tp := chi.URLParam(req, "type")
|
||||
assert.EqualValues(t, "issues", tp)
|
||||
route = 0
|
||||
})
|
||||
|
||||
r.Get("/{type:issues|pulls}/{index}", func(resp http.ResponseWriter, req *http.Request) {
|
||||
username := chi.URLParam(req, "username")
|
||||
assert.EqualValues(t, "gitea", username)
|
||||
reponame := chi.URLParam(req, "reponame")
|
||||
assert.EqualValues(t, "gitea", reponame)
|
||||
tp := chi.URLParam(req, "type")
|
||||
assert.EqualValues(t, "issues", tp)
|
||||
index := chi.URLParam(req, "index")
|
||||
assert.EqualValues(t, "1", index)
|
||||
route = 1
|
||||
})
|
||||
}, func(resp http.ResponseWriter, req *http.Request) {
|
||||
resp.WriteHeader(200)
|
||||
})
|
||||
|
||||
r.Group("/issues/{index}", func() {
|
||||
r.Get("/view", func(resp http.ResponseWriter, req *http.Request) {
|
||||
username := chi.URLParam(req, "username")
|
||||
assert.EqualValues(t, "gitea", username)
|
||||
reponame := chi.URLParam(req, "reponame")
|
||||
assert.EqualValues(t, "gitea", reponame)
|
||||
index := chi.URLParam(req, "index")
|
||||
assert.EqualValues(t, "1", index)
|
||||
route = 2
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
req, err := http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues", nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||
assert.EqualValues(t, 0, route)
|
||||
|
||||
req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1", nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||
assert.EqualValues(t, 1, route)
|
||||
|
||||
req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1/view", nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||
assert.EqualValues(t, 2, route)
|
||||
}
|
||||
|
||||
func TestRoute3(t *testing.T) {
|
||||
buff := bytes.NewBufferString("")
|
||||
recorder := httptest.NewRecorder()
|
||||
recorder.Body = buff
|
||||
|
||||
var route int
|
||||
|
||||
m := NewRoute()
|
||||
r := NewRoute()
|
||||
r.Mount("/api/v1", m)
|
||||
|
||||
m.Group("/repos", func() {
|
||||
m.Group("/{username}/{reponame}", func() {
|
||||
m.Group("/branch_protections", func() {
|
||||
m.Get("", func(resp http.ResponseWriter, req *http.Request) {
|
||||
route = 0
|
||||
})
|
||||
m.Post("", func(resp http.ResponseWriter, req *http.Request) {
|
||||
route = 1
|
||||
})
|
||||
m.Group("/{name}", func() {
|
||||
m.Get("", func(resp http.ResponseWriter, req *http.Request) {
|
||||
route = 2
|
||||
})
|
||||
m.Patch("", func(resp http.ResponseWriter, req *http.Request) {
|
||||
route = 3
|
||||
})
|
||||
m.Delete("", func(resp http.ResponseWriter, req *http.Request) {
|
||||
route = 4
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
req, err := http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||
assert.EqualValues(t, 0, route)
|
||||
|
||||
req, err = http.NewRequest("POST", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
assert.EqualValues(t, http.StatusOK, recorder.Code, http.StatusOK)
|
||||
assert.EqualValues(t, 1, route)
|
||||
|
||||
req, err = http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||
assert.EqualValues(t, 2, route)
|
||||
|
||||
req, err = http.NewRequest("PATCH", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||
assert.EqualValues(t, 3, route)
|
||||
|
||||
req, err = http.NewRequest("DELETE", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||
assert.EqualValues(t, 4, route)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue