Skip to content

Commit 28a42c7

Browse files
committed
Merge remote-tracking branch 'upstream/release/v1.23' into release/dcs/v1.23
2 parents 74aa845 + a2c6ecc commit 28a42c7

File tree

12 files changed

+134
-21
lines changed

12 files changed

+134
-21
lines changed

modules/httplib/request.go

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bytes"
99
"context"
1010
"crypto/tls"
11+
"errors"
1112
"fmt"
1213
"io"
1314
"net"
@@ -101,6 +102,9 @@ func (r *Request) Param(key, value string) *Request {
101102

102103
// Body adds request raw body. It supports string, []byte and io.Reader as body.
103104
func (r *Request) Body(data any) *Request {
105+
if r == nil {
106+
return nil
107+
}
104108
switch t := data.(type) {
105109
case nil: // do nothing
106110
case string:
@@ -193,6 +197,9 @@ func (r *Request) getResponse() (*http.Response, error) {
193197
// Response executes request client gets response manually.
194198
// Caller MUST close the response body if no error occurs
195199
func (r *Request) Response() (*http.Response, error) {
200+
if r == nil {
201+
return nil, errors.New("invalid request")
202+
}
196203
return r.getResponse()
197204
}
198205

modules/lfstransfer/backend/backend.go

+4-8
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,13 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
7070
g.logger.Log("json marshal error", err)
7171
return nil, err
7272
}
73-
url := g.server.JoinPath("objects/batch").String()
7473
headers := map[string]string{
7574
headerAuthorization: g.authToken,
7675
headerGiteaInternalAuth: g.internalAuth,
7776
headerAccept: mimeGitLFS,
7877
headerContentType: mimeGitLFS,
7978
}
80-
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
79+
req := newInternalRequestLFS(g.ctx, g.server.JoinPath("objects/batch").String(), http.MethodPost, headers, bodyBytes)
8180
resp, err := req.Response()
8281
if err != nil {
8382
g.logger.Log("http request error", err)
@@ -179,13 +178,12 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
179178
g.logger.Log("argument id incorrect")
180179
return nil, 0, transfer.ErrCorruptData
181180
}
182-
url := action.Href
183181
headers := map[string]string{
184182
headerAuthorization: g.authToken,
185183
headerGiteaInternalAuth: g.internalAuth,
186184
headerAccept: mimeOctetStream,
187185
}
188-
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
186+
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodGet, headers, nil)
189187
resp, err := req.Response()
190188
if err != nil {
191189
return nil, 0, fmt.Errorf("failed to get response: %w", err)
@@ -225,15 +223,14 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
225223
g.logger.Log("argument id incorrect")
226224
return transfer.ErrCorruptData
227225
}
228-
url := action.Href
229226
headers := map[string]string{
230227
headerAuthorization: g.authToken,
231228
headerGiteaInternalAuth: g.internalAuth,
232229
headerContentType: mimeOctetStream,
233230
headerContentLength: strconv.FormatInt(size, 10),
234231
}
235232

236-
req := newInternalRequestLFS(g.ctx, url, http.MethodPut, headers, nil)
233+
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPut, headers, nil)
237234
req.Body(r)
238235
resp, err := req.Response()
239236
if err != nil {
@@ -274,14 +271,13 @@ func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (trans
274271
// the server sent no verify action
275272
return transfer.SuccessStatus(), nil
276273
}
277-
url := action.Href
278274
headers := map[string]string{
279275
headerAuthorization: g.authToken,
280276
headerGiteaInternalAuth: g.internalAuth,
281277
headerAccept: mimeGitLFS,
282278
headerContentType: mimeGitLFS,
283279
}
284-
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
280+
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPost, headers, bodyBytes)
285281
resp, err := req.Response()
286282
if err != nil {
287283
return transfer.NewStatus(transfer.StatusInternalServerError), err

modules/lfstransfer/backend/lock.go

+5-8
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,13 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) {
4343
g.logger.Log("json marshal error", err)
4444
return nil, err
4545
}
46-
url := g.server.String()
4746
headers := map[string]string{
4847
headerAuthorization: g.authToken,
4948
headerGiteaInternalAuth: g.internalAuth,
5049
headerAccept: mimeGitLFS,
5150
headerContentType: mimeGitLFS,
5251
}
53-
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
52+
req := newInternalRequestLFS(g.ctx, g.server.String(), http.MethodPost, headers, bodyBytes)
5453
resp, err := req.Response()
5554
if err != nil {
5655
g.logger.Log("http request error", err)
@@ -95,14 +94,13 @@ func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
9594
g.logger.Log("json marshal error", err)
9695
return err
9796
}
98-
url := g.server.JoinPath(lock.ID(), "unlock").String()
9997
headers := map[string]string{
10098
headerAuthorization: g.authToken,
10199
headerGiteaInternalAuth: g.internalAuth,
102100
headerAccept: mimeGitLFS,
103101
headerContentType: mimeGitLFS,
104102
}
105-
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
103+
req := newInternalRequestLFS(g.ctx, g.server.JoinPath(lock.ID(), "unlock").String(), http.MethodPost, headers, bodyBytes)
106104
resp, err := req.Response()
107105
if err != nil {
108106
g.logger.Log("http request error", err)
@@ -176,16 +174,15 @@ func (g *giteaLockBackend) Range(cursor string, limit int, iter func(transfer.Lo
176174
}
177175

178176
func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, error) {
179-
urlq := g.server.JoinPath() // get a copy
180-
urlq.RawQuery = v.Encode()
181-
url := urlq.String()
177+
serverURLWithQuery := g.server.JoinPath() // get a copy
178+
serverURLWithQuery.RawQuery = v.Encode()
182179
headers := map[string]string{
183180
headerAuthorization: g.authToken,
184181
headerGiteaInternalAuth: g.internalAuth,
185182
headerAccept: mimeGitLFS,
186183
headerContentType: mimeGitLFS,
187184
}
188-
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
185+
req := newInternalRequestLFS(g.ctx, serverURLWithQuery.String(), http.MethodGet, headers, nil)
189186
resp, err := req.Response()
190187
if err != nil {
191188
g.logger.Log("http request error", err)

modules/lfstransfer/backend/util.go

+48-4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ import (
88
"fmt"
99
"io"
1010
"net/http"
11+
"net/url"
12+
"strings"
1113

1214
"code.gitea.io/gitea/modules/httplib"
1315
"code.gitea.io/gitea/modules/private"
16+
"code.gitea.io/gitea/modules/setting"
17+
"code.gitea.io/gitea/modules/util"
1418

1519
"github.com/charmbracelet/git-lfs-transfer/transfer"
1620
)
@@ -57,8 +61,7 @@ const (
5761

5862
// Operations enum
5963
const (
60-
opNone = iota
61-
opDownload
64+
opDownload = iota + 1
6265
opUpload
6366
)
6467

@@ -86,8 +89,49 @@ func statusCodeToErr(code int) error {
8689
}
8790
}
8891

89-
func newInternalRequestLFS(ctx context.Context, url, method string, headers map[string]string, body any) *httplib.Request {
90-
req := private.NewInternalRequest(ctx, url, method)
92+
func toInternalLFSURL(s string) string {
93+
pos1 := strings.Index(s, "://")
94+
if pos1 == -1 {
95+
return ""
96+
}
97+
appSubURLWithSlash := setting.AppSubURL + "/"
98+
pos2 := strings.Index(s[pos1+3:], appSubURLWithSlash)
99+
if pos2 == -1 {
100+
return ""
101+
}
102+
routePath := s[pos1+3+pos2+len(appSubURLWithSlash):]
103+
fields := strings.SplitN(routePath, "/", 3)
104+
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
105+
return ""
106+
}
107+
return setting.LocalURL + "api/internal/repo/" + routePath
108+
}
109+
110+
func isInternalLFSURL(s string) bool {
111+
if !strings.HasPrefix(s, setting.LocalURL) {
112+
return false
113+
}
114+
u, err := url.Parse(s)
115+
if err != nil {
116+
return false
117+
}
118+
routePath := util.PathJoinRelX(u.Path)
119+
subRoutePath, cut := strings.CutPrefix(routePath, "api/internal/repo/")
120+
if !cut {
121+
return false
122+
}
123+
fields := strings.SplitN(subRoutePath, "/", 3)
124+
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
125+
return false
126+
}
127+
return true
128+
}
129+
130+
func newInternalRequestLFS(ctx context.Context, internalURL, method string, headers map[string]string, body any) *httplib.Request {
131+
if !isInternalLFSURL(internalURL) {
132+
return nil
133+
}
134+
req := private.NewInternalRequest(ctx, internalURL, method)
91135
for k, v := range headers {
92136
req.Header(k, v)
93137
}
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package backend
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"code.gitea.io/gitea/modules/setting"
11+
"code.gitea.io/gitea/modules/test"
12+
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestToInternalLFSURL(t *testing.T) {
17+
defer test.MockVariableValue(&setting.LocalURL, "http://localurl/")()
18+
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
19+
cases := []struct {
20+
url string
21+
expected string
22+
}{
23+
{"http://appurl/any", ""},
24+
{"http://appurl/sub/any", ""},
25+
{"http://appurl/sub/owner/repo/any", ""},
26+
{"http://appurl/sub/owner/repo/info/any", ""},
27+
{"http://appurl/sub/owner/repo/info/lfs/any", "http://localurl/api/internal/repo/owner/repo/info/lfs/any"},
28+
}
29+
for _, c := range cases {
30+
assert.Equal(t, c.expected, toInternalLFSURL(c.url), c.url)
31+
}
32+
}
33+
34+
func TestIsInternalLFSURL(t *testing.T) {
35+
defer test.MockVariableValue(&setting.LocalURL, "http://localurl/")()
36+
defer test.MockVariableValue(&setting.InternalToken, "mock-token")()
37+
cases := []struct {
38+
url string
39+
expected bool
40+
}{
41+
{"", false},
42+
{"http://otherurl/api/internal/repo/owner/repo/info/lfs/any", false},
43+
{"http://localurl/api/internal/repo/owner/repo/info/lfs/any", true},
44+
{"http://localurl/api/internal/repo/owner/repo/info", false},
45+
{"http://localurl/api/internal/misc/owner/repo/info/lfs/any", false},
46+
{"http://localurl/api/internal/owner/repo/info/lfs/any", false},
47+
{"http://localurl/api/internal/foo/bar", false},
48+
}
49+
for _, c := range cases {
50+
req := newInternalRequestLFS(context.Background(), c.url, "GET", nil, nil)
51+
assert.Equal(t, c.expected, req != nil, c.url)
52+
assert.Equal(t, c.expected, isInternalLFSURL(c.url), c.url)
53+
}
54+
}

modules/private/internal.go

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ func NewInternalRequest(ctx context.Context, url, method string) *httplib.Reques
4040
Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
4141
}
4242

43+
if !strings.HasPrefix(url, setting.LocalURL) {
44+
log.Fatal("Invalid internal request URL: %q", url)
45+
}
46+
4347
req := httplib.NewRequest(url, method).
4448
SetContext(ctx).
4549
Header("X-Real-IP", getClientIP()).

templates/user/auth/signin_inner.tmpl

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
</div>
6060
</div>
6161

62+
{{if or .EnablePasskeyAuth .ShowRegistrationButton}}
6263
<div class="ui container fluid">
6364
<div class="ui attached segment header top tw-max-w-2xl tw-m-auto tw-flex tw-flex-col tw-items-center">
6465
{{if .EnablePasskeyAuth}}
@@ -74,3 +75,4 @@
7475
{{end}}
7576
</div>
7677
</div>
78+
{{end}}

tests/integration/git_lfs_ssh_test.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,14 @@ func TestGitLFSSSH(t *testing.T) {
5555
return strings.Contains(s, "POST /api/internal/repo/user2/repo1.git/info/lfs/objects/batch")
5656
})
5757
countUpload := slices.ContainsFunc(routerCalls, func(s string) bool {
58-
return strings.Contains(s, "PUT /user2/repo1.git/info/lfs/objects/")
58+
return strings.Contains(s, "PUT /api/internal/repo/user2/repo1.git/info/lfs/objects/")
59+
})
60+
nonAPIRequests := slices.ContainsFunc(routerCalls, func(s string) bool {
61+
fields := strings.Fields(s)
62+
return !strings.HasPrefix(fields[1], "/api/")
5963
})
6064
assert.NotZero(t, countBatch)
6165
assert.NotZero(t, countUpload)
66+
assert.Zero(t, nonAPIRequests)
6267
})
6368
}

tests/mssql.ini.tmpl

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ SIGNING_KEY = none
4545
SSH_DOMAIN = localhost
4646
HTTP_PORT = 3003
4747
ROOT_URL = http://localhost:3003/
48+
LOCAL_ROOT_URL = http://127.0.0.1:3003/
4849
DISABLE_SSH = false
4950
SSH_LISTEN_HOST = localhost
5051
SSH_PORT = 2201

tests/mysql.ini.tmpl

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ SIGNING_KEY = none
4747
SSH_DOMAIN = localhost
4848
HTTP_PORT = 3001
4949
ROOT_URL = http://localhost:3001/
50+
LOCAL_ROOT_URL = http://127.0.0.1:3001/
5051
DISABLE_SSH = false
5152
SSH_LISTEN_HOST = localhost
5253
SSH_PORT = 2201

tests/pgsql.ini.tmpl

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ SIGNING_KEY = none
4646
SSH_DOMAIN = localhost
4747
HTTP_PORT = 3002
4848
ROOT_URL = http://localhost:3002/
49+
LOCAL_ROOT_URL = http://127.0.0.1:3002/
4950
DISABLE_SSH = false
5051
SSH_LISTEN_HOST = localhost
5152
SSH_PORT = 2202

tests/sqlite.ini.tmpl

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ SIGNING_KEY = none
4141
SSH_DOMAIN = localhost
4242
HTTP_PORT = 3003
4343
ROOT_URL = http://localhost:3003/
44+
LOCAL_ROOT_URL = http://127.0.0.1:3003/
4445
DISABLE_SSH = false
4546
SSH_LISTEN_HOST = localhost
4647
SSH_PORT = 2203

0 commit comments

Comments
 (0)