Add multipart support for new resumable uploads API endpoint (#15510)

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Claudio Costa
2020-09-24 09:33:53 +02:00
коммит произвёл GitHub
родитель 8118cac350
Коммит 605f96fbea
3 изменённых файлов: 143 добавлений и 8 удалений

Просмотреть файл

@@ -4,6 +4,9 @@
package api4 package api4
import ( import (
"errors"
"io"
"mime/multipart"
"net/http" "net/http"
"github.com/mattermost/mattermost-server/v5/audit" "github.com/mattermost/mattermost-server/v5/audit"
@@ -95,18 +98,38 @@ func uploadData(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if r.ContentLength > (us.FileSize - us.FileOffset) {
c.Err = model.NewAppError("uploadData", "api.upload.upload_data.invalid_content_length",
nil, "", http.StatusBadRequest)
return
}
if us.UserId != c.App.Session().UserId || !c.App.SessionHasPermissionToChannel(*c.App.Session(), us.ChannelId, model.PERMISSION_UPLOAD_FILE) { if us.UserId != c.App.Session().UserId || !c.App.SessionHasPermissionToChannel(*c.App.Session(), us.ChannelId, model.PERMISSION_UPLOAD_FILE) {
c.SetPermissionError(model.PERMISSION_UPLOAD_FILE) c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
return return
} }
info, err := c.App.UploadData(us, r.Body) boundary, parseErr := parseMultipartRequestHeader(r)
if parseErr != nil && !errors.Is(parseErr, http.ErrNotMultipart) {
c.Err = model.NewAppError("uploadData", "api.upload.upload_data.invalid_content_type",
nil, parseErr.Error(), http.StatusBadRequest)
return
}
var rd io.Reader
if boundary != "" {
mr := multipart.NewReader(r.Body, boundary)
p, partErr := mr.NextPart()
if partErr != nil {
c.Err = model.NewAppError("uploadData", "api.upload.upload_data.multipart_error",
nil, partErr.Error(), http.StatusBadRequest)
return
}
rd = p
} else {
if r.ContentLength > (us.FileSize - us.FileOffset) {
c.Err = model.NewAppError("uploadData", "api.upload.upload_data.invalid_content_length",
nil, "", http.StatusBadRequest)
return
}
rd = r.Body
}
info, err := c.App.UploadData(us, rd)
if err != nil { if err != nil {
c.Err = err c.Err = err
return return

Просмотреть файл

@@ -6,6 +6,7 @@ package api4
import ( import (
"bytes" "bytes"
"io" "io"
"mime/multipart"
"net/http" "net/http"
"testing" "testing"
@@ -193,7 +194,12 @@ func TestUploadData(t *testing.T) {
}) })
t.Run("bad content-length", func(t *testing.T) { t.Run("bad content-length", func(t *testing.T) {
info, resp := th.Client.UploadData(us.Id, bytes.NewReader(append(data, 0x00))) u, resp := th.Client.CreateUpload(us)
require.Nil(t, resp.Error)
require.NotEmpty(t, u)
require.Equal(t, http.StatusCreated, resp.StatusCode)
info, resp := th.Client.UploadData(u.Id, bytes.NewReader(append(data, 0x00)))
require.Nil(t, info) require.Nil(t, info)
require.Error(t, resp.Error) require.Error(t, resp.Error)
require.Equal(t, "api.upload.upload_data.invalid_content_length", resp.Error.Id) require.Equal(t, "api.upload.upload_data.invalid_content_length", resp.Error.Id)
@@ -240,3 +246,101 @@ func TestUploadData(t *testing.T) {
require.Equal(t, file, data) require.Equal(t, file, data)
}) })
} }
func TestUploadDataMultipart(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
if *th.App.Config().FileSettings.DriverName == "" {
t.Skip("skipping because no file driver is enabled")
}
us := &model.UploadSession{
Id: model.NewId(),
Type: model.UploadTypeAttachment,
CreateAt: model.GetMillis(),
UserId: th.BasicUser.Id,
ChannelId: th.BasicChannel.Id,
Filename: "upload",
FileSize: 8 * 1024 * 1024,
}
us, resp := th.Client.CreateUpload(us)
require.Nil(t, resp.Error)
require.NotNil(t, us)
require.NotEmpty(t, us)
data := randomBytes(t, int(us.FileSize))
genMultipartData := func(t *testing.T, data []byte) (io.Reader, string) {
mpData := &bytes.Buffer{}
mpWriter := multipart.NewWriter(mpData)
part, err := mpWriter.CreateFormFile("data", us.Filename)
require.Nil(t, err)
n, err := part.Write(data)
require.Nil(t, err)
require.Equal(t, len(data), n)
err = mpWriter.Close()
require.Nil(t, err)
return mpData, mpWriter.FormDataContentType()
}
t.Run("bad content-type", func(t *testing.T) {
info, resp := th.Client.DoUploadFile("/uploads/"+us.Id, data, "multipart/form-data;")
require.Nil(t, info)
require.Error(t, resp.Error)
require.Equal(t, "api.upload.upload_data.invalid_content_type", resp.Error.Id)
})
t.Run("success", func(t *testing.T) {
mpData, contentType := genMultipartData(t, data)
req, err := http.NewRequest("POST", th.Client.ApiUrl+"/uploads/"+us.Id, mpData)
require.Nil(t, err)
req.Header.Set("Content-Type", contentType)
req.Header.Set(model.HEADER_AUTH, th.Client.AuthType+" "+th.Client.AuthToken)
res, err := th.Client.HttpClient.Do(req)
require.Nil(t, err)
info := model.FileInfoFromJson(res.Body)
res.Body.Close()
require.NotEmpty(t, info)
require.Equal(t, us.Filename, info.Name)
file, resp := th.Client.GetFile(info.Id)
require.Nil(t, resp.Error)
require.Equal(t, file, data)
})
t.Run("resume success", func(t *testing.T) {
mpData, contentType := genMultipartData(t, data[:5*1024*1024])
u, resp := th.Client.CreateUpload(us)
require.Nil(t, resp.Error)
require.NotNil(t, u)
require.NotEmpty(t, u)
req, err := http.NewRequest("POST", th.Client.ApiUrl+"/uploads/"+u.Id, mpData)
require.Nil(t, err)
req.Header.Set("Content-Type", contentType)
req.Header.Set(model.HEADER_AUTH, th.Client.AuthType+" "+th.Client.AuthToken)
res, err := th.Client.HttpClient.Do(req)
require.Nil(t, err)
require.Equal(t, http.StatusNoContent, res.StatusCode)
require.Equal(t, int64(0), res.ContentLength)
mpData, contentType = genMultipartData(t, data[5*1024*1024:])
req, err = http.NewRequest("POST", th.Client.ApiUrl+"/uploads/"+u.Id, mpData)
require.Nil(t, err)
req.Header.Set("Content-Type", contentType)
req.Header.Set(model.HEADER_AUTH, th.Client.AuthType+" "+th.Client.AuthToken)
res, err = th.Client.HttpClient.Do(req)
require.Nil(t, err)
info := model.FileInfoFromJson(res.Body)
res.Body.Close()
require.NotEmpty(t, info)
require.Equal(t, u.Filename, info.Name)
file, resp := th.Client.GetFile(info.Id)
require.Nil(t, resp.Error)
require.Equal(t, file, data)
})
}

Просмотреть файл

@@ -2734,6 +2734,14 @@
"id": "api.upload.upload_data.invalid_content_length", "id": "api.upload.upload_data.invalid_content_length",
"translation": "Invalid Content-Length." "translation": "Invalid Content-Length."
}, },
{
"id": "api.upload.upload_data.invalid_content_type",
"translation": "Invalid Content-Type for multipart upload."
},
{
"id": "api.upload.upload_data.multipart_error",
"translation": "Failed to process the multipart data."
},
{ {
"id": "api.user.activate_mfa.email_and_ldap_only.app_error", "id": "api.user.activate_mfa.email_and_ldap_only.app_error",
"translation": "MFA is not available for this account type." "translation": "MFA is not available for this account type."