Repository navigation
Expand file tree
/
Copy pathlfs_routes.v
More file actions
183 lines (171 loc) · 5.28 KB
/
Copy pathlfs_routes.v
File metadata and controls
183 lines (171 loc) · 5.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright (c) 2019-2026 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by a GPL license that can be found in the LICENSE file.
module main
import x.json2 as json
import os
import veb
struct LfsBatchRequest {
operation string
transfers []string
objects []LfsBatchObject
}
struct LfsBatchObject {
oid string
size i64
}
struct LfsAction {
href string
header map[string]string @[omitempty]
}
struct LfsObjectError {
code int
message string
}
struct LfsBatchObjectResponse {
oid string
size i64
actions map[string]LfsAction @[omitempty]
error LfsObjectError @[omitempty]
}
struct LfsBatchResponse {
transfer string
objects []LfsBatchObjectResponse
}
fn (mut app App) lfs_authenticate(mut ctx Context, repo Repo, required_scope string) bool {
username, credential := ctx.extract_user_credentials() or {
ctx.set_authenticate_headers()
ctx.send_unauthorized()
return false
}
if credential.starts_with('gldt_') {
if _ := app.authenticate_deploy_token(repo.id, username, credential, required_scope, ctx.ip()) {
return true
}
ctx.send_unauthorized()
return false
}
require_write := required_scope == deploy_token_scope_write_lfs
user := app.user_from_basic_credentials(ctx, require_write) or {
ctx.send_unauthorized()
return false
}
allowed := if require_write {
app.user_can_write_repo(user.id, repo)
} else {
app.user_has_repo_read_access(user.id, repo)
}
if !allowed {
ctx.send_not_found()
}
return allowed
}
fn (mut app App) lfs_object_href(repo Repo, oid string, size i64) string {
return '${app.generate_clone_url(repo)}/info/lfs/objects/${oid}?size=${size}'
}
@['/:username/:repo_name/info/lfs/objects/batch'; post]
pub fn (mut app App) lfs_batch(mut ctx Context, username string, repo_name string) veb.Result {
clean_repo_name := if repo_name.ends_with('.git') {
repo_name[..repo_name.len - 4]
} else {
repo_name
}
repo := app.find_repo_by_name_and_username(clean_repo_name, username) or { return ctx.not_found() }
request := json.decode[LfsBatchRequest](ctx.req.data) or {
return ctx.api_error_response(400, 'Bad Request', 'invalid Git LFS batch request')
}
if request.operation !in ['download', 'upload'] || request.objects.len > 1000 {
return ctx.api_error_response(400, 'Bad Request', 'invalid Git LFS operation')
}
if request.operation == 'upload' {
if !app.lfs_authenticate(mut ctx, repo, deploy_token_scope_write_lfs) {
return veb.no_result()
}
} else if !repo.is_public && !app.lfs_authenticate(mut ctx, repo, deploy_token_scope_read_lfs) {
return veb.no_result()
}
mut objects := []LfsBatchObjectResponse{cap: request.objects.len}
for object in request.objects {
if !valid_lfs_oid(object.oid) || object.size < 0 || object.size > max_lfs_object_size {
objects << LfsBatchObjectResponse{
oid: object.oid
size: object.size
error: LfsObjectError{
code: 422
message: 'invalid object id or size'
}
}
continue
}
exists := app.repo_has_lfs_object(repo.id, object.oid)
if request.operation == 'download' && !exists {
objects << LfsBatchObjectResponse{
oid: object.oid
size: object.size
error: LfsObjectError{
code: 404
message: 'LFS object not found'
}
}
continue
}
mut actions := map[string]LfsAction{}
if (request.operation == 'download' && exists) || (request.operation == 'upload' && !exists) {
actions[request.operation] = LfsAction{
href: app.lfs_object_href(repo, object.oid, object.size)
header: {
'Accept': 'application/vnd.git-lfs'
}
}
}
objects << LfsBatchObjectResponse{
oid: object.oid
size: object.size
actions: actions
}
}
ctx.set_content_type('application/vnd.git-lfs+json')
return ctx.ok(json.encode(LfsBatchResponse{
transfer: 'basic'
objects: objects
}))
}
@['/:username/:repo_name/info/lfs/objects/:oid'; put]
pub fn (mut app App) lfs_upload_object(mut ctx Context, username string, repo_name string,
oid string) veb.Result {
clean_repo_name := if repo_name.ends_with('.git') {
repo_name[..repo_name.len - 4]
} else {
repo_name
}
repo := app.find_repo_by_name_and_username(clean_repo_name, username) or { return ctx.not_found() }
if !app.lfs_authenticate(mut ctx, repo, deploy_token_scope_write_lfs) {
return veb.no_result()
}
expected_size := (ctx.query['size'] or { '-1' }).i64()
app.store_lfs_object(repo.id, oid, expected_size, ctx.req.data.bytes()) or {
return ctx.api_error_response(422, 'Unprocessable Entity', err.msg())
}
ctx.res.status_code = 200
ctx.res.status_msg = 'OK'
return ctx.ok('')
}
@['/:username/:repo_name/info/lfs/objects/:oid']
pub fn (mut app App) lfs_download_object(mut ctx Context, username string, repo_name string,
oid string) veb.Result {
clean_repo_name := if repo_name.ends_with('.git') {
repo_name[..repo_name.len - 4]
} else {
repo_name
}
repo := app.find_repo_by_name_and_username(clean_repo_name, username) or { return ctx.not_found() }
if !repo.is_public && !app.lfs_authenticate(mut ctx, repo, deploy_token_scope_read_lfs) {
return veb.no_result()
}
if !app.repo_has_lfs_object(repo.id, oid) {
return ctx.not_found()
}
data := os.read_bytes(app.lfs_object_path(oid)) or { return ctx.not_found() }
ctx.set_content_type('application/octet-stream')
ctx.set_header(.content_length, data.len.str())
return ctx.ok(data.bytestr())
}