From 6701b4d0c82272a9d46b9c1a1057ddde87497f0b Mon Sep 17 00:00:00 2001 From: e <747342561@qq.com> Date: Wed, 3 Jun 2020 10:40:23 +0800 Subject: [PATCH 01/14] get pre url --- modules/storage/local.go | 4 ++++ modules/storage/minio.go | 20 +++++++++++++++++--- modules/storage/storage.go | 1 + routers/repo/attachment.go | 31 +++++++++++++++++++++++++++++++ routers/routes/routes.go | 1 + 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/modules/storage/local.go b/modules/storage/local.go index 076a7f5e2..310d1883c 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -68,3 +68,7 @@ func (l *LocalStorage) Delete(path string) error { func (l *LocalStorage) PresignedGetURL(path string, fileName string) (string,error) { return "",nil } + +func (l *LocalStorage) PresignedPutURL(path string) (string,error) { + return "",nil +} diff --git a/modules/storage/minio.go b/modules/storage/minio.go index 5fafa9965..ddbb9328b 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -18,7 +18,10 @@ var ( _ ObjectStorage = &MinioStorage{} ) -const PRESIGNED_URL_EXPIRE_TIME = time.Hour * 24 * 7 +const ( + PresignedGetUrlExpireTime = time.Hour * 24 * 7 + PresignedPutUrlExpireTime = time.Hour * 24 * 7 +) // MinioStorage returns a minio bucket storage type MinioStorage struct { @@ -73,14 +76,25 @@ func (m *MinioStorage) Delete(path string) error { return m.client.RemoveObject(m.bucket, m.buildMinioPath(path)) } -//Get Presigned URL +//Get Presigned URL for get object func (m *MinioStorage) PresignedGetURL(path string, fileName string) (string,error) { // Set request parameters for content-disposition. reqParams := make(url.Values) reqParams.Set("response-content-disposition", "attachment; filename=\"" + fileName + "\"") var preURL *url.URL - preURL,err := m.client.PresignedGetObject(m.bucket, m.buildMinioPath(path), PRESIGNED_URL_EXPIRE_TIME, reqParams) + preURL,err := m.client.PresignedGetObject(m.bucket, m.buildMinioPath(path), PresignedGetUrlExpireTime, reqParams) + if err != nil { + return "",err + } + + return preURL.String(),nil +} + +//Get Presigned URL for put object +func (m *MinioStorage) PresignedPutURL(path string) (string,error) { + var preURL *url.URL + preURL,err := m.client.PresignedPutObject(m.bucket, m.buildMinioPath(path), PresignedPutUrlExpireTime) if err != nil { return "",err } diff --git a/modules/storage/storage.go b/modules/storage/storage.go index 912454ac9..811315cab 100644 --- a/modules/storage/storage.go +++ b/modules/storage/storage.go @@ -22,6 +22,7 @@ type ObjectStorage interface { Open(path string) (io.ReadCloser, error) Delete(path string) error PresignedGetURL(path string, fileName string) (string, error) + PresignedPutURL(path string) (string, error) } // Copy copys a file from source ObjectStorage to dest ObjectStorage diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index 05b94ca22..5797c999c 100644 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -15,6 +15,8 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/upload" + + gouuid "github.com/satori/go.uuid" ) func RenderAttachmentSettings(ctx *context.Context) { @@ -193,3 +195,32 @@ func increaseDownloadCount(attach *models.Attachment, dataSet *models.Dataset) e return nil } + +// Get a presigned url for put object +func GetPresignedPutObjectURL(ctx *context.Context) { + if !setting.Attachment.Enabled { + ctx.Error(404, "attachment is not enabled") + return + } + + uuid := "" + url := "" + + if setting.Attachment.StoreType == storage.MinioStorageType { + uuid = gouuid.NewV4().String() + url, err := storage.Attachments.PresignedPutURL(models.AttachmentRelativePath(uuid)) + if err != nil { + ctx.ServerError("PresignedPutURL", err) + return + } + log.Trace(url) + } else { + ctx.Error(404, "storage type is not enabled") + return + } + + ctx.JSON(200, map[string]string{ + "uuid": uuid, + "url": url, + }) +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index f7d2f1d1e..9f0943deb 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -519,6 +519,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/attachments", func() { m.Post("", repo.UploadAttachment) m.Post("/delete", repo.DeleteAttachment) + m.Get("/get_pre_url", repo.GetPresignedPutObjectURL) }, reqSignIn) m.Group("/:username", func() { From eca90ee75c8ec1383ceaf00f4e15aba8f5a79751 Mon Sep 17 00:00:00 2001 From: e <747342561@qq.com> Date: Wed, 3 Jun 2020 10:47:05 +0800 Subject: [PATCH 02/14] fix bug --- routers/repo/attachment.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index 5797c999c..5ef85b820 100644 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -203,24 +203,22 @@ func GetPresignedPutObjectURL(ctx *context.Context) { return } - uuid := "" - url := "" - if setting.Attachment.StoreType == storage.MinioStorageType { - uuid = gouuid.NewV4().String() + uuid := gouuid.NewV4().String() url, err := storage.Attachments.PresignedPutURL(models.AttachmentRelativePath(uuid)) if err != nil { ctx.ServerError("PresignedPutURL", err) return } - log.Trace(url) + + ctx.JSON(200, map[string]string{ + "uuid": uuid, + "url": url, + }) } else { ctx.Error(404, "storage type is not enabled") return } - ctx.JSON(200, map[string]string{ - "uuid": uuid, - "url": url, - }) + } From 7dae4d3a069cb1e1a86ff8a877e4705f2aaa61ac Mon Sep 17 00:00:00 2001 From: e <747342561@qq.com> Date: Wed, 3 Jun 2020 14:53:18 +0800 Subject: [PATCH 03/14] AddAttachment --- models/attachment.go | 10 ++++++++++ modules/storage/local.go | 4 ++++ modules/storage/minio.go | 21 +++++++++++++++++++++ modules/storage/storage.go | 1 + modules/upload/filetype.go | 6 +++++- routers/repo/attachment.go | 35 +++++++++++++++++++++++++++++++++++ routers/routes/routes.go | 1 + 7 files changed, 77 insertions(+), 1 deletion(-) diff --git a/models/attachment.go b/models/attachment.go index aa5f4e8d1..621f34216 100644 --- a/models/attachment.go +++ b/models/attachment.go @@ -303,3 +303,13 @@ func (a *Attachment) LinkedDataSet() (*Dataset, error) { } return nil, nil } + +// InsertAttachment insert a record into attachment. +func InsertAttachment(attach *Attachment) (_ *Attachment, err error) { + + if _, err := x.Insert(attach); err != nil { + return nil, err + } + + return attach, nil +} diff --git a/modules/storage/local.go b/modules/storage/local.go index 310d1883c..4d2f28029 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -72,3 +72,7 @@ func (l *LocalStorage) PresignedGetURL(path string, fileName string) (string,err func (l *LocalStorage) PresignedPutURL(path string) (string,error) { return "",nil } + +func (l *LocalStorage) HasObject(path string) (bool,error) { + return false,nil +} diff --git a/modules/storage/minio.go b/modules/storage/minio.go index ddbb9328b..dfbe39558 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -101,3 +101,24 @@ func (m *MinioStorage) PresignedPutURL(path string) (string,error) { return preURL.String(),nil } + +//check if has the object +func (m *MinioStorage) HasObject(path string) (bool,error) { + hasObject := false + // Create a done channel to control 'ListObjects' go routine. + doneCh := make(chan struct{}) + + // Indicate to our routine to exit cleanly upon return. + defer close(doneCh) + + objectCh := m.client.ListObjects(m.bucket, m.buildMinioPath(path), false, doneCh) + for object := range objectCh { + if object.Err != nil { + return hasObject, object.Err + } + + hasObject = true + } + + return hasObject,nil +} diff --git a/modules/storage/storage.go b/modules/storage/storage.go index 811315cab..d06ec7208 100644 --- a/modules/storage/storage.go +++ b/modules/storage/storage.go @@ -23,6 +23,7 @@ type ObjectStorage interface { Delete(path string) error PresignedGetURL(path string, fileName string) (string, error) PresignedPutURL(path string) (string, error) + HasObject(path string) (bool, error) } // Copy copys a file from source ObjectStorage to dest ObjectStorage diff --git a/modules/upload/filetype.go b/modules/upload/filetype.go index 2ab326d11..ff88eb140 100644 --- a/modules/upload/filetype.go +++ b/modules/upload/filetype.go @@ -31,11 +31,15 @@ func (err ErrFileTypeForbidden) Error() string { func VerifyAllowedContentType(buf []byte, allowedTypes []string) error { fileType := http.DetectContentType(buf) + return VerifyFileType(fileType, allowedTypes) +} + +func VerifyFileType(fileType string, allowedTypes []string) error { for _, t := range allowedTypes { t := strings.Trim(t, " ") if t == "*/*" || t == fileType || - // Allow directives after type, like 'text/plain; charset=utf-8' + // Allow directives after type, like 'text/plain; charset=utf-8' strings.HasPrefix(fileType, t+";") { return nil } diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index 5ef85b820..1a1e9f186 100644 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -203,6 +203,12 @@ func GetPresignedPutObjectURL(ctx *context.Context) { return } + err := upload.VerifyFileType(ctx.Params("file_type"), strings.Split(setting.Attachment.AllowedTypes, ",")) + if err != nil { + ctx.Error(400, err.Error()) + return + } + if setting.Attachment.StoreType == storage.MinioStorageType { uuid := gouuid.NewV4().String() url, err := storage.Attachments.PresignedPutURL(models.AttachmentRelativePath(uuid)) @@ -219,6 +225,35 @@ func GetPresignedPutObjectURL(ctx *context.Context) { ctx.Error(404, "storage type is not enabled") return } +} +// AddAttachment response for add attachment record +func AddAttachment(ctx *context.Context) { + uuid := ctx.Query("uuid") + has,err := storage.Attachments.HasObject(models.AttachmentRelativePath(uuid)) + if err != nil { + ctx.ServerError("HasObject", err) + return + } + if !has { + ctx.Error(404, "attachment has not been uploaded") + return + } + + _, err = models.InsertAttachment(&models.Attachment{ + UUID: uuid, + UploaderID: ctx.User.ID, + Name: ctx.Query("file_name"), + Size: ctx.QueryInt64("size"), + }) + + if err != nil { + ctx.Error(500, fmt.Sprintf("InsertAttachment: %v", err)) + return + } + + ctx.JSON(200, map[string]string{ + "result_code": "0", + }) } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 9f0943deb..42e83242b 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -520,6 +520,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("", repo.UploadAttachment) m.Post("/delete", repo.DeleteAttachment) m.Get("/get_pre_url", repo.GetPresignedPutObjectURL) + m.Post("/add", repo.AddAttachment) }, reqSignIn) m.Group("/:username", func() { From b713a3d1e1c05e00b2760ad574ec047188d2941b Mon Sep 17 00:00:00 2001 From: e <747342561@qq.com> Date: Wed, 3 Jun 2020 16:31:15 +0800 Subject: [PATCH 04/14] front end --- web_src/js/index.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/web_src/js/index.js b/web_src/js/index.js index f96123c5b..bc30bf19c 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -305,7 +305,7 @@ function retrieveImageFromClipboardAsBlob(pasteEvent, callback) { } } } - +/* function uploadFile(file, callback) { const xhr = new XMLHttpRequest(); @@ -321,6 +321,38 @@ function uploadFile(file, callback) { formData.append('file', file, file.name); xhr.send(formData); } +*/ +function uploadFile(file, callback) { + retrieveNewURL(file, url => { + // 上传文件到服务器 + uploadFileToS3(file, url) + }) +} + +// 发请求到server获取上传URL。 +function retrieveNewURL(file, cb) { + $.get(`${AppSubUrl}/attachments/get_pre_url`, (url) => { + cb(url) + }) + console.info(url) +} + +// 使用XMLHttpRequest来上传文件到S3。 +function uploadFileToS3(file, url) { + var xhr = new XMLHttpRequest () + xhr.open('PUT', url, true) + xhr.setRequestHeader('X-Csrf-Token', csrf); + const formData = new FormData(); + formData.append('file', file, file.name); + xhr.send(formData); + /* + xhr.send(file) + xhr.onload = () => { + if (xhr.status == 200) { + $('#status').text(`Uploaded ${file.name}.`) + } + }*/ +} function reload() { window.location.reload(); From aae58b0fd1bb9d69cee60368a7752a1c57a3b7fb Mon Sep 17 00:00:00 2001 From: e <747342561@qq.com> Date: Wed, 3 Jun 2020 17:14:55 +0800 Subject: [PATCH 05/14] fix web bug --- web_src/js/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web_src/js/index.js b/web_src/js/index.js index bc30bf19c..4748de046 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -323,9 +323,10 @@ function uploadFile(file, callback) { } */ function uploadFile(file, callback) { + var xhr = new XMLHttpRequest(); retrieveNewURL(file, url => { // 上传文件到服务器 - uploadFileToS3(file, url) + uploadFileToS3(xhr, file, url) }) } @@ -338,8 +339,7 @@ function retrieveNewURL(file, cb) { } // 使用XMLHttpRequest来上传文件到S3。 -function uploadFileToS3(file, url) { - var xhr = new XMLHttpRequest () +function uploadFileToS3(xhr, file, url) { xhr.open('PUT', url, true) xhr.setRequestHeader('X-Csrf-Token', csrf); const formData = new FormData(); From 32774a0261f615f9e5bf7a709014db3ceddcf9bb Mon Sep 17 00:00:00 2001 From: e <747342561@qq.com> Date: Thu, 4 Jun 2020 09:24:35 +0800 Subject: [PATCH 06/14] upload --- web_src/js/index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web_src/js/index.js b/web_src/js/index.js index 4748de046..612d31750 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -323,10 +323,9 @@ function uploadFile(file, callback) { } */ function uploadFile(file, callback) { - var xhr = new XMLHttpRequest(); retrieveNewURL(file, url => { // 上传文件到服务器 - uploadFileToS3(xhr, file, url) + uploadFileToS3(file, url) }) } @@ -335,11 +334,11 @@ function retrieveNewURL(file, cb) { $.get(`${AppSubUrl}/attachments/get_pre_url`, (url) => { cb(url) }) - console.info(url) } // 使用XMLHttpRequest来上传文件到S3。 -function uploadFileToS3(xhr, file, url) { +function uploadFileToS3(file, url) { + var xhr = new XMLHttpRequest () xhr.open('PUT', url, true) xhr.setRequestHeader('X-Csrf-Token', csrf); const formData = new FormData(); From ee836b931275048cb965f67bbaa21b7985f759ed Mon Sep 17 00:00:00 2001 From: e <747342561@qq.com> Date: Thu, 4 Jun 2020 15:23:50 +0800 Subject: [PATCH 07/14] web pre --- routers/repo/dataset.go | 10 +++++++++ templates/repo/datasets/dataset.tmpl | 2 +- web_src/js/index.js | 39 ++++++------------------------------ 3 files changed, 17 insertions(+), 34 deletions(-) diff --git a/routers/repo/dataset.go b/routers/repo/dataset.go index 33d949702..47a425c84 100644 --- a/routers/repo/dataset.go +++ b/routers/repo/dataset.go @@ -1,6 +1,7 @@ package repo import ( + "code.gitea.io/gitea/modules/storage" "sort" "code.gitea.io/gitea/models" @@ -8,6 +9,8 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" + + gouuid "github.com/satori/go.uuid" ) const ( @@ -77,6 +80,13 @@ func DatasetIndex(ctx *context.Context) { ctx.Data["dataset"] = dataset ctx.Data["Attachments"] = attachments ctx.Data["IsOwner"] = true + uuid := gouuid.NewV4().String() + url, err := storage.Attachments.PresignedPutURL(models.AttachmentRelativePath(uuid)) + if err != nil { + ctx.ServerError("PresignedPutURL", err) + } + ctx.Data["uuid"] = uuid + ctx.Data["url"] = url renderAttachmentSettings(ctx) ctx.HTML(200, tplIndex) diff --git a/templates/repo/datasets/dataset.tmpl b/templates/repo/datasets/dataset.tmpl index 29f90f9af..be0837273 100644 --- a/templates/repo/datasets/dataset.tmpl +++ b/templates/repo/datasets/dataset.tmpl @@ -2,7 +2,7 @@