From 1c718da16c0d2420465066807996b10db993959b Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 28 Jun 2026 14:14:39 +0200 Subject: [PATCH] fix(api): support HEAD requests on all API GET endpoints (#38245) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #38226 ## Summary Add `chi_middleware.GetHead` as the first `BeforeRouting` middleware on the API router. This makes every API `GET` endpoint automatically handle `HEAD` requests, as required by RFC 9110 §9.3.2. Previously, `HEAD` requests to endpoints like `GET /repos/{owner}/{repo}/git/commits/{sha}` returned `405 Method Not Allowed`. The web router already used this same middleware (see `routers/web/web.go:261`), so this aligns API behaviour with the web router. ## Changes - `routers/api/v1/api.go`: add `chi_middleware.GetHead` middleware to the API router - `tests/integration/api_repo_git_commits_test.go`: add `TestAPIReposGitCommitsHEAD` verifying HEAD returns 200 on a valid ref and 404 (not 405) on a missing ref --- routers/api/v1/api.go | 4 ++++ tests/integration/api_repo_git_commits_test.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 50135c0d7a..6d78121190 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -99,6 +99,7 @@ import ( _ "gitea.dev/routers/api/v1/swagger" // for swagger generation "gitea.com/go-chi/binding" + chi_middleware "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" ) @@ -950,6 +951,9 @@ func checkDeprecatedAuthMethods(ctx *context.APIContext) { func Routes() *web.Router { m := web.NewRouter() + // redirect HEAD requests to GET if no HEAD handler is defined (RFC 9110 §9.3.2) + m.BeforeRouting(chi_middleware.GetHead) + if setting.CORSConfig.Enabled { m.BeforeRouting(cors.Handler(cors.Options{ AllowedOrigins: setting.CORSConfig.AllowDomain, diff --git a/tests/integration/api_repo_git_commits_test.go b/tests/integration/api_repo_git_commits_test.go index 504d8e933c..9d460a20c0 100644 --- a/tests/integration/api_repo_git_commits_test.go +++ b/tests/integration/api_repo_git_commits_test.go @@ -56,6 +56,23 @@ func TestAPIReposGitCommits(t *testing.T) { } } +func TestAPIReposGitCommitsHEAD(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + + // HEAD on a valid ref must return 200 (RFC 9110 §9.3.2) + req := NewRequestf(t, "HEAD", "/api/v1/repos/%s/repo1/git/commits/master", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + // HEAD on a missing sha must return 404, not 405 + req = NewRequestf(t, "HEAD", "/api/v1/repos/%s/repo1/git/commits/12345", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) +} + func TestAPIReposGitCommitList(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})