loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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 <teabot@gitea.io>
(cherry picked from commit 70c126e6184872a6ac63cae2f327fc745b25d1d7)

authored by

KN4CK3R
Giteabot
and committed by
Earl Warren
47a913d4 e1592974

+87 -32
+29 -10
routers/api/v1/repo/release_attachment.go
··· 4 4 package repo 5 5 6 6 import ( 7 + "io" 7 8 "net/http" 9 + "strings" 8 10 9 11 repo_model "code.gitea.io/gitea/models/repo" 10 12 "code.gitea.io/gitea/modules/log" ··· 154 156 // - application/json 155 157 // consumes: 156 158 // - multipart/form-data 159 + // - application/octet-stream 157 160 // parameters: 158 161 // - name: owner 159 162 // in: path ··· 180 183 // in: formData 181 184 // description: attachment to upload 182 185 // type: file 183 - // required: true 186 + // required: false 184 187 // responses: 185 188 // "201": 186 189 // "$ref": "#/responses/Attachment" ··· 202 205 } 203 206 204 207 // 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") 209 229 } 210 - defer file.Close() 211 230 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 215 234 } 216 235 217 236 // 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{ 219 238 Name: filename, 220 239 UploaderID: ctx.Doer.ID, 221 240 RepoID: ctx.Repo.Repository.ID,
+3 -3
services/attachment/attachment.go
··· 44 44 } 45 45 46 46 // UploadAttachment upload new attachment into storage and update database 47 - func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, opts *repo_model.Attachment) (*repo_model.Attachment, error) { 47 + func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, attach *repo_model.Attachment) (*repo_model.Attachment, error) { 48 48 buf := make([]byte, 1024) 49 49 n, _ := util.ReadAtMost(file, buf) 50 50 buf = buf[:n] 51 51 52 - if err := upload.Verify(buf, opts.Name, allowedTypes); err != nil { 52 + if err := upload.Verify(buf, attach.Name, allowedTypes); err != nil { 53 53 return nil, err 54 54 } 55 55 56 - return NewAttachment(ctx, opts, io.MultiReader(bytes.NewReader(buf), file), fileSize) 56 + return NewAttachment(ctx, attach, io.MultiReader(bytes.NewReader(buf), file), fileSize) 57 57 }
+3 -3
templates/swagger/v1_json.tmpl
··· 12828 12828 }, 12829 12829 "post": { 12830 12830 "consumes": [ 12831 - "multipart/form-data" 12831 + "multipart/form-data", 12832 + "application/octet-stream" 12832 12833 ], 12833 12834 "produces": [ 12834 12835 "application/json" ··· 12871 12872 "type": "file", 12872 12873 "description": "attachment to upload", 12873 12874 "name": "attachment", 12874 - "in": "formData", 12875 - "required": true 12875 + "in": "formData" 12876 12876 } 12877 12877 ], 12878 12878 "responses": {
+52 -16
tests/integration/api_releases_test.go
··· 262 262 263 263 filename := "image.png" 264 264 buff := generateImg() 265 - body := &bytes.Buffer{} 266 265 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) 274 267 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)() 279 270 280 - var attachment *api.Attachment 281 - DecodeJSON(t, resp, &attachment) 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) 282 280 283 - assert.EqualValues(t, "test-asset", attachment.Name) 284 - assert.EqualValues(t, 104, attachment.Size) 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) 314 + 315 + var attachment *api.Attachment 316 + DecodeJSON(t, resp, &attachment) 317 + 318 + assert.EqualValues(t, "stream.bin", attachment.Name) 319 + assert.EqualValues(t, 104, attachment.Size) 320 + }) 285 321 }