Skip to content

Commit 70c126e

Browse files
KN4CK3RGiteaBot
andauthored
Add support for API blob upload of release attachments (#29507)
Fixes #29502 Our endpoint is not Github compatible. https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#upload-a-release-asset --------- Co-authored-by: Giteabot <[email protected]>
1 parent 6465f94 commit 70c126e

File tree

4 files changed

+87
-32
lines changed

4 files changed

+87
-32
lines changed

routers/api/v1/repo/release_attachment.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
package repo
55

66
import (
7+
"io"
78
"net/http"
9+
"strings"
810

911
repo_model "code.gitea.io/gitea/models/repo"
1012
"code.gitea.io/gitea/modules/log"
@@ -154,6 +156,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
154156
// - application/json
155157
// consumes:
156158
// - multipart/form-data
159+
// - application/octet-stream
157160
// parameters:
158161
// - name: owner
159162
// in: path
@@ -180,7 +183,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
180183
// in: formData
181184
// description: attachment to upload
182185
// type: file
183-
// required: true
186+
// required: false
184187
// responses:
185188
// "201":
186189
// "$ref": "#/responses/Attachment"
@@ -202,20 +205,36 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
202205
}
203206

204207
// Get uploaded file from request
205-
file, header, err := ctx.Req.FormFile("attachment")
206-
if err != nil {
207-
ctx.Error(http.StatusInternalServerError, "GetFile", err)
208-
return
208+
var content io.ReadCloser
209+
var filename string
210+
var size int64 = -1
211+
212+
if strings.HasPrefix(strings.ToLower(ctx.Req.Header.Get("Content-Type")), "multipart/form-data") {
213+
file, header, err := ctx.Req.FormFile("attachment")
214+
if err != nil {
215+
ctx.Error(http.StatusInternalServerError, "GetFile", err)
216+
return
217+
}
218+
defer file.Close()
219+
220+
content = file
221+
size = header.Size
222+
filename = header.Filename
223+
if name := ctx.FormString("name"); name != "" {
224+
filename = name
225+
}
226+
} else {
227+
content = ctx.Req.Body
228+
filename = ctx.FormString("name")
209229
}
210-
defer file.Close()
211230

212-
filename := header.Filename
213-
if query := ctx.FormString("name"); query != "" {
214-
filename = query
231+
if filename == "" {
232+
ctx.Error(http.StatusBadRequest, "CreateReleaseAttachment", "Could not determine name of attachment.")
233+
return
215234
}
216235

217236
// Create a new attachment and save the file
218-
attach, err := attachment.UploadAttachment(ctx, file, setting.Repository.Release.AllowedTypes, header.Size, &repo_model.Attachment{
237+
attach, err := attachment.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, size, &repo_model.Attachment{
219238
Name: filename,
220239
UploaderID: ctx.Doer.ID,
221240
RepoID: ctx.Repo.Repository.ID,

services/attachment/attachment.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ func NewAttachment(ctx context.Context, attach *repo_model.Attachment, file io.R
3939
}
4040

4141
// UploadAttachment upload new attachment into storage and update database
42-
func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, opts *repo_model.Attachment) (*repo_model.Attachment, error) {
42+
func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, attach *repo_model.Attachment) (*repo_model.Attachment, error) {
4343
buf := make([]byte, 1024)
4444
n, _ := util.ReadAtMost(file, buf)
4545
buf = buf[:n]
4646

47-
if err := upload.Verify(buf, opts.Name, allowedTypes); err != nil {
47+
if err := upload.Verify(buf, attach.Name, allowedTypes); err != nil {
4848
return nil, err
4949
}
5050

51-
return NewAttachment(ctx, opts, io.MultiReader(bytes.NewReader(buf), file), fileSize)
51+
return NewAttachment(ctx, attach, io.MultiReader(bytes.NewReader(buf), file), fileSize)
5252
}

templates/swagger/v1_json.tmpl

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/integration/api_releases_test.go

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -262,24 +262,60 @@ func TestAPIUploadAssetRelease(t *testing.T) {
262262

263263
filename := "image.png"
264264
buff := generateImg()
265-
body := &bytes.Buffer{}
266265

267-
writer := multipart.NewWriter(body)
268-
part, err := writer.CreateFormFile("attachment", filename)
269-
assert.NoError(t, err)
270-
_, err = io.Copy(part, &buff)
271-
assert.NoError(t, err)
272-
err = writer.Close()
273-
assert.NoError(t, err)
266+
assetURL := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner.Name, repo.Name, r.ID)
274267

275-
req := NewRequestWithBody(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets?name=test-asset", owner.Name, repo.Name, r.ID), body).
276-
AddTokenAuth(token)
277-
req.Header.Add("Content-Type", writer.FormDataContentType())
278-
resp := MakeRequest(t, req, http.StatusCreated)
268+
t.Run("multipart/form-data", func(t *testing.T) {
269+
defer tests.PrintCurrentTest(t)()
270+
271+
body := &bytes.Buffer{}
272+
273+
writer := multipart.NewWriter(body)
274+
part, err := writer.CreateFormFile("attachment", filename)
275+
assert.NoError(t, err)
276+
_, err = io.Copy(part, bytes.NewReader(buff.Bytes()))
277+
assert.NoError(t, err)
278+
err = writer.Close()
279+
assert.NoError(t, err)
280+
281+
req := NewRequestWithBody(t, http.MethodPost, assetURL, bytes.NewReader(body.Bytes())).
282+
AddTokenAuth(token).
283+
SetHeader("Content-Type", writer.FormDataContentType())
284+
resp := MakeRequest(t, req, http.StatusCreated)
285+
286+
var attachment *api.Attachment
287+
DecodeJSON(t, resp, &attachment)
288+
289+
assert.EqualValues(t, filename, attachment.Name)
290+
assert.EqualValues(t, 104, attachment.Size)
291+
292+
req = NewRequestWithBody(t, http.MethodPost, assetURL+"?name=test-asset", bytes.NewReader(body.Bytes())).
293+
AddTokenAuth(token).
294+
SetHeader("Content-Type", writer.FormDataContentType())
295+
resp = MakeRequest(t, req, http.StatusCreated)
296+
297+
var attachment2 *api.Attachment
298+
DecodeJSON(t, resp, &attachment2)
299+
300+
assert.EqualValues(t, "test-asset", attachment2.Name)
301+
assert.EqualValues(t, 104, attachment2.Size)
302+
})
303+
304+
t.Run("application/octet-stream", func(t *testing.T) {
305+
defer tests.PrintCurrentTest(t)()
306+
307+
req := NewRequestWithBody(t, http.MethodPost, assetURL, bytes.NewReader(buff.Bytes())).
308+
AddTokenAuth(token)
309+
MakeRequest(t, req, http.StatusBadRequest)
310+
311+
req = NewRequestWithBody(t, http.MethodPost, assetURL+"?name=stream.bin", bytes.NewReader(buff.Bytes())).
312+
AddTokenAuth(token)
313+
resp := MakeRequest(t, req, http.StatusCreated)
279314

280-
var attachment *api.Attachment
281-
DecodeJSON(t, resp, &attachment)
315+
var attachment *api.Attachment
316+
DecodeJSON(t, resp, &attachment)
282317

283-
assert.EqualValues(t, "test-asset", attachment.Name)
284-
assert.EqualValues(t, 104, attachment.Size)
318+
assert.EqualValues(t, "stream.bin", attachment.Name)
319+
assert.EqualValues(t, 104, attachment.Size)
320+
})
285321
}

0 commit comments

Comments
 (0)