mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 09:31:53 +01:00 
			
		
		
		
	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>
This commit is contained in:
		
							parent
							
								
									6465f94a2d
								
							
						
					
					
						commit
						70c126e618
					
				| @ -4,7 +4,9 @@ | ||||
| package repo | ||||
| 
 | ||||
| import ( | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	repo_model "code.gitea.io/gitea/models/repo" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| @ -154,6 +156,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) { | ||||
| 	// - application/json | ||||
| 	// consumes: | ||||
| 	// - multipart/form-data | ||||
| 	// - application/octet-stream | ||||
| 	// parameters: | ||||
| 	// - name: owner | ||||
| 	//   in: path | ||||
| @ -180,7 +183,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) { | ||||
| 	//   in: formData | ||||
| 	//   description: attachment to upload | ||||
| 	//   type: file | ||||
| 	//   required: true | ||||
| 	//   required: false | ||||
| 	// responses: | ||||
| 	//   "201": | ||||
| 	//     "$ref": "#/responses/Attachment" | ||||
| @ -202,20 +205,36 @@ func CreateReleaseAttachment(ctx *context.APIContext) { | ||||
| 	} | ||||
| 
 | ||||
| 	// Get uploaded file from request | ||||
| 	file, header, err := ctx.Req.FormFile("attachment") | ||||
| 	if err != nil { | ||||
| 		ctx.Error(http.StatusInternalServerError, "GetFile", err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer file.Close() | ||||
| 	var content io.ReadCloser | ||||
| 	var filename string | ||||
| 	var size int64 = -1 | ||||
| 
 | ||||
| 	filename := header.Filename | ||||
| 	if query := ctx.FormString("name"); query != "" { | ||||
| 		filename = query | ||||
| 	if strings.HasPrefix(strings.ToLower(ctx.Req.Header.Get("Content-Type")), "multipart/form-data") { | ||||
| 		file, header, err := ctx.Req.FormFile("attachment") | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusInternalServerError, "GetFile", err) | ||||
| 			return | ||||
| 		} | ||||
| 		defer file.Close() | ||||
| 
 | ||||
| 		content = file | ||||
| 		size = header.Size | ||||
| 		filename = header.Filename | ||||
| 		if name := ctx.FormString("name"); name != "" { | ||||
| 			filename = name | ||||
| 		} | ||||
| 	} else { | ||||
| 		content = ctx.Req.Body | ||||
| 		filename = ctx.FormString("name") | ||||
| 	} | ||||
| 
 | ||||
| 	if filename == "" { | ||||
| 		ctx.Error(http.StatusBadRequest, "CreateReleaseAttachment", "Could not determine name of attachment.") | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Create a new attachment and save the file | ||||
| 	attach, err := attachment.UploadAttachment(ctx, file, setting.Repository.Release.AllowedTypes, header.Size, &repo_model.Attachment{ | ||||
| 	attach, err := attachment.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, size, &repo_model.Attachment{ | ||||
| 		Name:       filename, | ||||
| 		UploaderID: ctx.Doer.ID, | ||||
| 		RepoID:     ctx.Repo.Repository.ID, | ||||
|  | ||||
| @ -39,14 +39,14 @@ func NewAttachment(ctx context.Context, attach *repo_model.Attachment, file io.R | ||||
| } | ||||
| 
 | ||||
| // UploadAttachment upload new attachment into storage and update database | ||||
| func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, opts *repo_model.Attachment) (*repo_model.Attachment, error) { | ||||
| func UploadAttachment(ctx context.Context, file io.Reader, allowedTypes string, fileSize int64, attach *repo_model.Attachment) (*repo_model.Attachment, error) { | ||||
| 	buf := make([]byte, 1024) | ||||
| 	n, _ := util.ReadAtMost(file, buf) | ||||
| 	buf = buf[:n] | ||||
| 
 | ||||
| 	if err := upload.Verify(buf, opts.Name, allowedTypes); err != nil { | ||||
| 	if err := upload.Verify(buf, attach.Name, allowedTypes); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return NewAttachment(ctx, opts, io.MultiReader(bytes.NewReader(buf), file), fileSize) | ||||
| 	return NewAttachment(ctx, attach, io.MultiReader(bytes.NewReader(buf), file), fileSize) | ||||
| } | ||||
|  | ||||
							
								
								
									
										6
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							| @ -12343,7 +12343,8 @@ | ||||
|       }, | ||||
|       "post": { | ||||
|         "consumes": [ | ||||
|           "multipart/form-data" | ||||
|           "multipart/form-data", | ||||
|           "application/octet-stream" | ||||
|         ], | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
| @ -12386,8 +12387,7 @@ | ||||
|             "type": "file", | ||||
|             "description": "attachment to upload", | ||||
|             "name": "attachment", | ||||
|             "in": "formData", | ||||
|             "required": true | ||||
|             "in": "formData" | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|  | ||||
| @ -262,24 +262,60 @@ func TestAPIUploadAssetRelease(t *testing.T) { | ||||
| 
 | ||||
| 	filename := "image.png" | ||||
| 	buff := generateImg() | ||||
| 	body := &bytes.Buffer{} | ||||
| 
 | ||||
| 	writer := multipart.NewWriter(body) | ||||
| 	part, err := writer.CreateFormFile("attachment", filename) | ||||
| 	assert.NoError(t, err) | ||||
| 	_, err = io.Copy(part, &buff) | ||||
| 	assert.NoError(t, err) | ||||
| 	err = writer.Close() | ||||
| 	assert.NoError(t, err) | ||||
| 	assetURL := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner.Name, repo.Name, r.ID) | ||||
| 
 | ||||
| 	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). | ||||
| 		AddTokenAuth(token) | ||||
| 	req.Header.Add("Content-Type", writer.FormDataContentType()) | ||||
| 	resp := MakeRequest(t, req, http.StatusCreated) | ||||
| 	t.Run("multipart/form-data", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
| 
 | ||||
| 	var attachment *api.Attachment | ||||
| 	DecodeJSON(t, resp, &attachment) | ||||
| 		body := &bytes.Buffer{} | ||||
| 
 | ||||
| 	assert.EqualValues(t, "test-asset", attachment.Name) | ||||
| 	assert.EqualValues(t, 104, attachment.Size) | ||||
| 		writer := multipart.NewWriter(body) | ||||
| 		part, err := writer.CreateFormFile("attachment", filename) | ||||
| 		assert.NoError(t, err) | ||||
| 		_, err = io.Copy(part, bytes.NewReader(buff.Bytes())) | ||||
| 		assert.NoError(t, err) | ||||
| 		err = writer.Close() | ||||
| 		assert.NoError(t, err) | ||||
| 
 | ||||
| 		req := NewRequestWithBody(t, http.MethodPost, assetURL, bytes.NewReader(body.Bytes())). | ||||
| 			AddTokenAuth(token). | ||||
| 			SetHeader("Content-Type", writer.FormDataContentType()) | ||||
| 		resp := MakeRequest(t, req, http.StatusCreated) | ||||
| 
 | ||||
| 		var attachment *api.Attachment | ||||
| 		DecodeJSON(t, resp, &attachment) | ||||
| 
 | ||||
| 		assert.EqualValues(t, filename, attachment.Name) | ||||
| 		assert.EqualValues(t, 104, attachment.Size) | ||||
| 
 | ||||
| 		req = NewRequestWithBody(t, http.MethodPost, assetURL+"?name=test-asset", bytes.NewReader(body.Bytes())). | ||||
| 			AddTokenAuth(token). | ||||
| 			SetHeader("Content-Type", writer.FormDataContentType()) | ||||
| 		resp = MakeRequest(t, req, http.StatusCreated) | ||||
| 
 | ||||
| 		var attachment2 *api.Attachment | ||||
| 		DecodeJSON(t, resp, &attachment2) | ||||
| 
 | ||||
| 		assert.EqualValues(t, "test-asset", attachment2.Name) | ||||
| 		assert.EqualValues(t, 104, attachment2.Size) | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("application/octet-stream", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
| 
 | ||||
| 		req := NewRequestWithBody(t, http.MethodPost, assetURL, bytes.NewReader(buff.Bytes())). | ||||
| 			AddTokenAuth(token) | ||||
| 		MakeRequest(t, req, http.StatusBadRequest) | ||||
| 
 | ||||
| 		req = NewRequestWithBody(t, http.MethodPost, assetURL+"?name=stream.bin", bytes.NewReader(buff.Bytes())). | ||||
| 			AddTokenAuth(token) | ||||
| 		resp := MakeRequest(t, req, http.StatusCreated) | ||||
| 
 | ||||
| 		var attachment *api.Attachment | ||||
| 		DecodeJSON(t, resp, &attachment) | ||||
| 
 | ||||
| 		assert.EqualValues(t, "stream.bin", attachment.Name) | ||||
| 		assert.EqualValues(t, 104, attachment.Size) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user