Skip to content

Commit 02031bd

Browse files
XZB-1248XZB-1248Suyunmeng
authored
feat(s3): add Content-Disposition header (#365)
* add(s3): add Content-Disposition header * Update driver.go Signed-off-by: XZB-1248 <[email protected]> * Update driver.go Signed-off-by: XZB-1248 <[email protected]> --------- Signed-off-by: XZB-1248 <[email protected]> Co-authored-by: XZB-1248 <[email protected]> Co-authored-by: Suyunjing <[email protected]>
1 parent 7726cb1 commit 02031bd

File tree

9 files changed

+63
-84
lines changed

9 files changed

+63
-84
lines changed

drivers/doubao/driver.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func (d *Doubao) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
145145
}
146146

147147
// 生成标准的Content-Disposition
148-
contentDisposition := generateContentDisposition(u.Name)
148+
contentDisposition := utils.GenerateContentDisposition(u.Name)
149149

150150
return &model.Link{
151151
URL: downloadUrl,

drivers/doubao/util.go

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -926,36 +926,6 @@ func getSigningKey(secretKey, dateStamp, region, service string) []byte {
926926
return kSigning
927927
}
928928

929-
// generateContentDisposition 生成符合RFC 5987标准的Content-Disposition头部
930-
func generateContentDisposition(filename string) string {
931-
// 按照RFC 2047进行编码,用于filename部分
932-
encodedName := urlEncode(filename)
933-
934-
// 按照RFC 5987进行编码,用于filename*部分
935-
encodedNameRFC5987 := encodeRFC5987(filename)
936-
937-
return fmt.Sprintf("attachment; filename=\"%s\"; filename*=utf-8''%s",
938-
encodedName, encodedNameRFC5987)
939-
}
940-
941-
// encodeRFC5987 按照RFC 5987规范编码字符串,适用于HTTP头部参数中的非ASCII字符
942-
func encodeRFC5987(s string) string {
943-
var buf strings.Builder
944-
for _, r := range []byte(s) {
945-
// 根据RFC 5987,只有字母、数字和部分特殊符号可以不编码
946-
if (r >= 'a' && r <= 'z') ||
947-
(r >= 'A' && r <= 'Z') ||
948-
(r >= '0' && r <= '9') ||
949-
r == '-' || r == '.' || r == '_' || r == '~' {
950-
buf.WriteByte(r)
951-
} else {
952-
// 其他字符都需要百分号编码
953-
fmt.Fprintf(&buf, "%%%02X", r)
954-
}
955-
}
956-
return buf.String()
957-
}
958-
959929
func randomString() string {
960930
const charset = "0123456789abcdefghijklmnopqrstuvwxyz"
961931
const length = 11 // 11位随机字符串

drivers/doubao_share/driver.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/OpenListTeam/OpenList/internal/driver"
1010
"github.com/OpenListTeam/OpenList/internal/errs"
1111
"github.com/OpenListTeam/OpenList/internal/model"
12+
"github.com/OpenListTeam/OpenList/pkg/utils"
1213
"github.com/go-resty/resty/v2"
1314
)
1415

@@ -105,7 +106,7 @@ func (d *DoubaoShare) Link(ctx context.Context, file model.Obj, args model.LinkA
105106
}
106107

107108
// 生成标准的Content-Disposition
108-
contentDisposition := generateContentDisposition(u.Name)
109+
contentDisposition := utils.GenerateContentDisposition(u.Name)
109110

110111
return &model.Link{
111112
URL: downloadUrl,

drivers/doubao_share/util.go

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"encoding/json"
66
"fmt"
77
"net/http"
8-
"net/url"
98
"path"
109
"regexp"
1110
"strings"
@@ -707,39 +706,3 @@ func (d *DoubaoShare) listVirtualDirectoryContent(dir model.Obj) ([]model.Obj, e
707706

708707
return objects, nil
709708
}
710-
711-
// generateContentDisposition 生成符合RFC 5987标准的Content-Disposition头部
712-
func generateContentDisposition(filename string) string {
713-
// 按照RFC 2047进行编码,用于filename部分
714-
encodedName := urlEncode(filename)
715-
716-
// 按照RFC 5987进行编码,用于filename*部分
717-
encodedNameRFC5987 := encodeRFC5987(filename)
718-
719-
return fmt.Sprintf("attachment; filename=\"%s\"; filename*=utf-8''%s",
720-
encodedName, encodedNameRFC5987)
721-
}
722-
723-
// encodeRFC5987 按照RFC 5987规范编码字符串,适用于HTTP头部参数中的非ASCII字符
724-
func encodeRFC5987(s string) string {
725-
var buf strings.Builder
726-
for _, r := range []byte(s) {
727-
// 根据RFC 5987,只有字母、数字和部分特殊符号可以不编码
728-
if (r >= 'a' && r <= 'z') ||
729-
(r >= 'A' && r <= 'Z') ||
730-
(r >= '0' && r <= '9') ||
731-
r == '-' || r == '.' || r == '_' || r == '~' {
732-
buf.WriteByte(r)
733-
} else {
734-
// 其他字符都需要百分号编码
735-
fmt.Fprintf(&buf, "%%%02X", r)
736-
}
737-
}
738-
return buf.String()
739-
}
740-
741-
func urlEncode(s string) string {
742-
s = url.QueryEscape(s)
743-
s = strings.ReplaceAll(s, "+", "%20")
744-
return s
745-
}

drivers/s3/driver.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/OpenListTeam/OpenList/internal/model"
1515
"github.com/OpenListTeam/OpenList/internal/stream"
1616
"github.com/OpenListTeam/OpenList/pkg/cron"
17+
"github.com/OpenListTeam/OpenList/pkg/utils"
1718
"github.com/OpenListTeam/OpenList/server/common"
1819
"github.com/aws/aws-sdk-go/aws/session"
1920
"github.com/aws/aws-sdk-go/service/s3"
@@ -81,19 +82,21 @@ func (d *S3) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]mo
8182

8283
func (d *S3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
8384
path := getKey(file.GetPath(), false)
84-
filename := stdpath.Base(path)
85-
disposition := fmt.Sprintf(`attachment; filename*=UTF-8''%s`, url.PathEscape(filename))
86-
if d.AddFilenameToDisposition {
87-
disposition = fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename))
88-
}
85+
fileName := stdpath.Base(path)
8986
input := &s3.GetObjectInput{
9087
Bucket: &d.Bucket,
9188
Key: &path,
9289
//ResponseContentDisposition: &disposition,
9390
}
91+
9492
if d.CustomHost == "" {
93+
disposition := fmt.Sprintf(`attachment; filename*=UTF-8''%s`, url.PathEscape(fileName))
94+
if d.AddFilenameToDisposition {
95+
disposition = utils.GenerateContentDisposition(fileName)
96+
}
9597
input.ResponseContentDisposition = &disposition
9698
}
99+
97100
req, _ := d.linkClient.GetObjectRequest(input)
98101
var link model.Link
99102
var err error
@@ -108,7 +111,7 @@ func (d *S3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*mo
108111
link.URL = strings.Replace(link.URL, "/"+d.Bucket, "", 1)
109112
}
110113
} else {
111-
if common.ShouldProxy(d, filename) {
114+
if common.ShouldProxy(d, fileName) {
112115
err = req.Sign()
113116
link.URL = req.HTTPRequest.URL.String()
114117
link.Header = req.HTTPRequest.Header

pkg/utils/http.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"strings"
7+
)
8+
9+
// GenerateContentDisposition 生成符合RFC 5987标准的Content-Disposition头部
10+
func GenerateContentDisposition(fileName string) string {
11+
// 按照RFC 2047进行编码,用于filename部分
12+
encodedName := urlEncode(fileName)
13+
14+
// 按照RFC 5987进行编码,用于filename*部分
15+
encodedNameRFC5987 := encodeRFC5987(fileName)
16+
17+
return fmt.Sprintf("attachment; filename=\"%s\"; filename*=utf-8''%s",
18+
encodedName, encodedNameRFC5987)
19+
}
20+
21+
// encodeRFC5987 按照RFC 5987规范编码字符串,适用于HTTP头部参数中的非ASCII字符
22+
func encodeRFC5987(s string) string {
23+
var buf strings.Builder
24+
for _, r := range []byte(s) {
25+
// 根据RFC 5987,只有字母、数字和部分特殊符号可以不编码
26+
if (r >= 'a' && r <= 'z') ||
27+
(r >= 'A' && r <= 'Z') ||
28+
(r >= '0' && r <= '9') ||
29+
r == '-' || r == '.' || r == '_' || r == '~' {
30+
buf.WriteByte(r)
31+
} else {
32+
// 其他字符都需要百分号编码
33+
fmt.Fprintf(&buf, "%%%02X", r)
34+
}
35+
}
36+
return buf.String()
37+
}
38+
39+
func urlEncode(s string) string {
40+
s = url.QueryEscape(s)
41+
s = strings.ReplaceAll(s, "+", "%20")
42+
return s
43+
}

server/common/proxy.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"io"
77
"net/http"
8-
"net/url"
98
"os"
109
"strings"
1110

@@ -93,7 +92,7 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
9392
}
9493
func attachHeader(w http.ResponseWriter, file model.Obj) {
9594
fileName := file.GetName()
96-
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, fileName, url.PathEscape(fileName)))
95+
w.Header().Set("Content-Disposition", utils.GenerateContentDisposition(fileName))
9796
w.Header().Set("Content-Type", utils.GetMimeType(fileName))
9897
w.Header().Set("Etag", GetEtag(file))
9998
}

server/handles/archive.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package handles
33
import (
44
"encoding/json"
55
"fmt"
6-
"net/url"
76
stdpath "path"
87

98
"github.com/OpenListTeam/OpenList/internal/task"
@@ -392,11 +391,11 @@ func ArchiveInternalExtract(c *gin.Context) {
392391
"Referrer-Policy": "no-referrer",
393392
"Cache-Control": "max-age=0, no-cache, no-store, must-revalidate",
394393
}
395-
filename := stdpath.Base(innerPath)
396-
headers["Content-Disposition"] = fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename))
394+
fileName := stdpath.Base(innerPath)
395+
headers["Content-Disposition"] = utils.GenerateContentDisposition(fileName)
397396
contentType := c.Request.Header.Get("Content-Type")
398397
if contentType == "" {
399-
contentType = utils.GetMimeType(filename)
398+
contentType = utils.GetMimeType(fileName)
400399
}
401400
c.DataFromReader(200, size, contentType, rc, headers)
402401
}

server/s3/backend.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,9 @@ func (b *s3Backend) GetObject(ctx context.Context, bucketName, objectName string
213213
}
214214

215215
meta := map[string]string{
216-
"Last-Modified": node.ModTime().Format(timeFormat),
217-
"Content-Type": utils.GetMimeType(fp),
216+
"Last-Modified": node.ModTime().Format(timeFormat),
217+
"Content-Disposition": utils.GenerateContentDisposition(file.GetName()),
218+
"Content-Type": utils.GetMimeType(fp),
218219
}
219220

220221
if val, ok := b.meta.Load(fp); ok {
@@ -328,7 +329,7 @@ func (b *s3Backend) PutObject(
328329
func (b *s3Backend) DeleteMulti(ctx context.Context, bucketName string, objects ...string) (result gofakes3.MultiDeleteResult, rerr error) {
329330
for _, object := range objects {
330331
if err := b.deleteObject(ctx, bucketName, object); err != nil {
331-
utils.Log.Errorf("serve s3", "delete object failed: %v", err)
332+
log.Errorf("delete object failed: %v", err)
332333
result.Error = append(result.Error, gofakes3.ErrorResult{
333334
Code: gofakes3.ErrInternal,
334335
Message: gofakes3.ErrInternal.Message(),

0 commit comments

Comments
 (0)