Continue flamego migration - fix Context methods and route handlers.

Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-25 12:07:16 +00:00
parent 5ea7e5f96d
commit f8ebb278df
2 changed files with 55 additions and 39 deletions

View File

@@ -15,6 +15,14 @@ import (
"gogs.io/gogs/internal/lfsutil"
)
// writeError writes an HTTP error response.
func writeError(w http.ResponseWriter, status int, text string) {
w.WriteHeader(status)
if text != "" {
w.Write([]byte(text))
}
}
// RegisterRoutes registers LFS routes using given router, and inherits all
// groups and middleware.
func RegisterRoutes(r flamego.Router) {
@@ -66,7 +74,7 @@ func authenticate(store Store) flamego.Handler {
}
if err == nil && store.IsTwoFactorEnabled(c.Request().Context(), user.ID) {
c.Error(http.StatusBadRequest, "Users with 2FA enabled are not allowed to authenticate via username and password.")
writeError(c.ResponseWriter(), http.StatusBadRequest, "Users with 2FA enabled are not allowed to authenticate via username and password.")
return
}
@@ -85,7 +93,7 @@ func authenticate(store Store) flamego.Handler {
if database.IsErrAccessTokenNotExist(err) {
askCredentials(c.ResponseWriter())
} else {
c.Status(http.StatusInternalServerError)
c.ResponseWriter().WriteHeader(http.StatusInternalServerError)
log.Error("Failed to authenticate by access token via password: %v", err)
}
return
@@ -108,7 +116,7 @@ func authorize(store Store, mode database.AccessMode) flamego.Handler {
owner, err := store.GetUserByUsername(c.Request().Context(), username)
if err != nil {
if database.IsErrUserNotExist(err) {
c.Status(http.StatusNotFound)
c.ResponseWriter().WriteHeader(http.StatusNotFound)
} else {
internalServerError(c.ResponseWriter())
log.Error("Failed to get user [name: %s]: %v", username, err)
@@ -119,7 +127,7 @@ func authorize(store Store, mode database.AccessMode) flamego.Handler {
repo, err := store.GetRepositoryByName(c.Request().Context(), owner.ID, reponame)
if err != nil {
if database.IsErrRepoNotExist(err) {
c.Status(http.StatusNotFound)
c.ResponseWriter().WriteHeader(http.StatusNotFound)
} else {
internalServerError(c.ResponseWriter())
log.Error("Failed to get repository [owner_id: %d, name: %s]: %v", owner.ID, reponame, err)
@@ -133,7 +141,7 @@ func authorize(store Store, mode database.AccessMode) flamego.Handler {
Private: repo.IsPrivate,
},
) {
c.Status(http.StatusNotFound)
c.ResponseWriter().WriteHeader(http.StatusNotFound)
return
}
@@ -156,7 +164,7 @@ func verifyHeader(key, value string, failCode int) flamego.Handler {
}
log.Trace("[LFS] HTTP header %q does not contain value %q", key, value)
c.Status(failCode)
c.ResponseWriter().WriteHeader(failCode)
}
}

View File

@@ -34,22 +34,30 @@ type HTTPContext struct {
AuthUser *database.User
}
// writeError writes an HTTP error response.
func writeError(w http.ResponseWriter, status int, text string) {
w.WriteHeader(status)
if text != "" {
w.Write([]byte(text))
}
}
// askCredentials responses HTTP header and status which informs client to provide credentials.
func askCredentials(c flamego.Context, status int, text string) {
c.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
c.Error(status, text)
c.ResponseWriter().Header().Set("WWW-Authenticate", "Basic realm=\".\"")
writeError(c.ResponseWriter(), status, text)
}
func HTTPContexter(store Store) flamego.Handler {
return func(c flamego.Context) {
if len(conf.HTTP.AccessControlAllowOrigin) > 0 {
// Set CORS headers for browser-based git clients
c.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin)
c.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent")
c.ResponseWriter().Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin)
c.ResponseWriter().Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent")
// Handle preflight OPTIONS request
if c.Req.Method == "OPTIONS" {
c.Status(http.StatusOK)
if c.Request().Method == "OPTIONS" {
c.ResponseWriter().WriteHeader(http.StatusOK)
return
}
}
@@ -59,26 +67,26 @@ func HTTPContexter(store Store) flamego.Handler {
repoName = strings.TrimSuffix(repoName, ".wiki")
isPull := c.Query("service") == "git-upload-pack" ||
strings.HasSuffix(c.Req.URL.Path, "git-upload-pack") ||
c.Req.Method == "GET"
strings.HasSuffix(c.Request().URL.Path, "git-upload-pack") ||
c.Request().Method == "GET"
owner, err := store.GetUserByUsername(c.Req.Context(), ownerName)
owner, err := store.GetUserByUsername(c.Request().Context(), ownerName)
if err != nil {
if database.IsErrUserNotExist(err) {
c.Status(http.StatusNotFound)
c.ResponseWriter().WriteHeader(http.StatusNotFound)
} else {
c.Status(http.StatusInternalServerError)
c.ResponseWriter().WriteHeader(http.StatusInternalServerError)
log.Error("Failed to get user [name: %s]: %v", ownerName, err)
}
return
}
repo, err := store.GetRepositoryByName(c.Req.Context(), owner.ID, repoName)
repo, err := store.GetRepositoryByName(c.Request().Context(), owner.ID, repoName)
if err != nil {
if database.IsErrRepoNotExist(err) {
c.Status(http.StatusNotFound)
c.ResponseWriter().WriteHeader(http.StatusNotFound)
} else {
c.Status(http.StatusInternalServerError)
c.ResponseWriter().WriteHeader(http.StatusInternalServerError)
log.Error("Failed to get repository [owner_id: %d, name: %s]: %v", owner.ID, repoName, err)
}
return
@@ -98,12 +106,12 @@ func HTTPContexter(store Store) flamego.Handler {
!strings.Contains(action, "info/") &&
!strings.Contains(action, "HEAD") &&
!strings.Contains(action, "objects/") {
c.Error(http.StatusBadRequest, fmt.Sprintf("Unrecognized action %q", action))
writeError(c.ResponseWriter(), http.StatusBadRequest, fmt.Sprintf("Unrecognized action %q", action))
return
}
// Handle HTTP Basic Authentication
authHead := c.Req.Header.Get("Authorization")
authHead := c.Request().Header.Get("Authorization")
if authHead == "" {
askCredentials(c, http.StatusUnauthorized, "")
return
@@ -120,9 +128,9 @@ func HTTPContexter(store Store) flamego.Handler {
return
}
authUser, err := store.AuthenticateUser(c.Req.Context(), authUsername, authPassword, -1)
authUser, err := store.AuthenticateUser(c.Request().Context(), authUsername, authPassword, -1)
if err != nil && !auth.IsErrBadCredentials(err) {
c.Status(http.StatusInternalServerError)
c.ResponseWriter().WriteHeader(http.StatusInternalServerError)
log.Error("Failed to authenticate user [name: %s]: %v", authUsername, err)
return
}
@@ -130,25 +138,25 @@ func HTTPContexter(store Store) flamego.Handler {
// If username and password combination failed, try again using either username
// or password as the token.
if authUser == nil {
authUser, err = context.AuthenticateByToken(store, c.Req.Context(), authUsername)
authUser, err = context.AuthenticateByToken(store, c.Request().Context(), authUsername)
if err != nil && !database.IsErrAccessTokenNotExist(err) {
c.Status(http.StatusInternalServerError)
c.ResponseWriter().WriteHeader(http.StatusInternalServerError)
log.Error("Failed to authenticate by access token via username: %v", err)
return
} else if database.IsErrAccessTokenNotExist(err) {
// Try again using the password field as the token.
authUser, err = context.AuthenticateByToken(store, c.Req.Context(), authPassword)
authUser, err = context.AuthenticateByToken(store, c.Request().Context(), authPassword)
if err != nil {
if database.IsErrAccessTokenNotExist(err) {
askCredentials(c, http.StatusUnauthorized, "")
} else {
c.Status(http.StatusInternalServerError)
c.ResponseWriter().WriteHeader(http.StatusInternalServerError)
log.Error("Failed to authenticate by access token via password: %v", err)
}
return
}
}
} else if store.IsTwoFactorEnabled(c.Req.Context(), authUser.ID) {
} else if store.IsTwoFactorEnabled(c.Request().Context(), authUser.ID) {
askCredentials(c, http.StatusUnauthorized, `User with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password
Please create and use personal access token on user settings page`)
return
@@ -160,7 +168,7 @@ Please create and use personal access token on user settings page`)
if isPull {
mode = database.AccessModeRead
}
if !database.Handle.Permissions().Authorize(c.Req.Context(), authUser.ID, repo.ID, mode,
if !database.Handle.Permissions().Authorize(c.Request().Context(), authUser.ID, repo.ID, mode,
database.AccessModeOptions{
OwnerID: repo.OwnerID,
Private: repo.IsPrivate,
@@ -171,7 +179,7 @@ Please create and use personal access token on user settings page`)
}
if !isPull && repo.IsMirror {
c.Error(http.StatusForbidden, "Mirror repository is read-only")
writeError(c.ResponseWriter(), http.StatusForbidden, "Mirror repository is read-only")
return
}
@@ -388,7 +396,7 @@ func getGitRepoPath(dir string) (string, error) {
func HTTP(c *HTTPContext) {
for _, route := range routes {
reqPath := strings.ToLower(c.Req.URL.Path)
reqPath := strings.ToLower(c.Request().URL.Path)
m := route.re.FindStringSubmatch(reqPath)
if m == nil {
continue
@@ -398,19 +406,19 @@ func HTTP(c *HTTPContext) {
// but we only want to output this message only if user is really trying to access
// Git HTTP endpoints.
if conf.Repository.DisableHTTPGit {
c.Error(http.StatusForbidden, "Interacting with repositories by HTTP protocol is disabled")
writeError(c.ResponseWriter(), http.StatusForbidden, "Interacting with repositories by HTTP protocol is disabled")
return
}
if route.method != c.Req.Method {
c.Error(http.StatusNotFound)
if route.method != c.Request().Method {
writeError(c.ResponseWriter(), http.StatusNotFound)
return
}
// 🚨 SECURITY: Prevent path traversal.
cleaned := pathutil.Clean(m[1])
if m[1] != "/"+cleaned {
c.Error(http.StatusBadRequest, "Request path contains suspicious characters")
writeError(c.ResponseWriter(), http.StatusBadRequest, "Request path contains suspicious characters")
return
}
@@ -418,13 +426,13 @@ func HTTP(c *HTTPContext) {
dir, err := getGitRepoPath(cleaned)
if err != nil {
log.Warn("HTTP.getGitRepoPath: %v", err)
c.Error(http.StatusNotFound)
writeError(c.ResponseWriter(), http.StatusNotFound)
return
}
route.handler(serviceHandler{
w: c.Resp,
r: c.Req.Request,
r: c.Request().Request,
dir: dir,
file: file,
@@ -437,5 +445,5 @@ func HTTP(c *HTTPContext) {
return
}
c.Error(http.StatusNotFound)
writeError(c.ResponseWriter(), http.StatusNotFound)
}