refactor(provider): re-implement deployment provider of aliyun oss with custom sdk, to lightweight package size

This commit is contained in:
Fu Diwei
2026-06-22 16:32:18 +08:00
committed by RHQYZ
parent bc6191e8eb
commit b14b88c865
11 changed files with 511 additions and 71 deletions
-1
View File
@@ -29,7 +29,6 @@ require (
github.com/alibabacloud-go/tea-utils/v2 v2.0.9
github.com/alibabacloud-go/vod-20170321/v4 v4.11.3
github.com/alibabacloud-go/waf-openapi-20211001/v7 v7.8.1
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.5.1
github.com/aws/aws-sdk-go-v2 v1.41.12
github.com/aws/aws-sdk-go-v2/config v1.32.23
github.com/aws/aws-sdk-go-v2/credentials v1.19.22
-2
View File
@@ -176,8 +176,6 @@ github.com/alibabacloud-go/vod-20170321/v4 v4.11.3 h1:xF9GzlR4Qr+H2MRu558rpJUG6s
github.com/alibabacloud-go/vod-20170321/v4 v4.11.3/go.mod h1:2NX/9lVaKpd1+1GEV5zUAzQFfK9pF8Wkx81ugAnHYiw=
github.com/alibabacloud-go/waf-openapi-20211001/v7 v7.8.1 h1:zCZIxEfoC3SmPx6x+7JzxXBPkQZ7eA19YFtWWJ80HMs=
github.com/alibabacloud-go/waf-openapi-20211001/v7 v7.8.1/go.mod h1:oP48y3ec8gOIGFXcxKEUMiQwO9mZmlAH4TFZZvInHik=
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.5.1 h1:vtiFd0hhPAbyYJjztl0wYUq/PqEGkIlDmVuTIy6zw8Y=
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.5.1/go.mod h1:FTzydeQVmR24FI0D6XWUOMKckjXehM/jgMn1xC+DA9M=
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
@@ -4,12 +4,14 @@ import (
"context"
"fmt"
"log/slog"
"strings"
"github.com/alibabacloud-go/tea/tea"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
"github.com/samber/lo"
"github.com/certimate-go/certimate/pkg/core"
cmgrimpl "github.com/certimate-go/certimate/pkg/core/certmgr/providers/aliyun-cas"
osssdk "github.com/certimate-go/certimate/pkg/sdk3rd/alibabacloud/oss"
)
type (
@@ -33,9 +35,10 @@ type DeployerConfig struct {
}
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *oss.Client
config *DeployerConfig
logger *slog.Logger
sdkClient *osssdk.Client
sdkCertmgr core.Certmgr
}
var _ Provider = (*Deployer)(nil)
@@ -45,15 +48,28 @@ func NewDeployer(config *DeployerConfig) (*Deployer, error) {
return nil, fmt.Errorf("the configuration of the deployer provider is nil")
}
client, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret, config.Region)
client, err := createSDKClient(config.AccessKeyId, config.AccessKeySecret, config.Region, config.Bucket)
if err != nil {
return nil, fmt.Errorf("could not create client: %w", err)
}
pcertmgr, err := cmgrimpl.NewCertmgr(&cmgrimpl.CertmgrConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
ResourceGroupId: config.ResourceGroupId,
Region: lo.
If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
Else("ap-southeast-1"),
})
if err != nil {
return nil, fmt.Errorf("could not create certmgr: %w", err)
}
return &Deployer{
config: config,
logger: slog.Default(),
sdkClient: client,
config: config,
logger: slog.Default(),
sdkClient: client,
sdkCertmgr: pcertmgr,
}, nil
}
@@ -63,6 +79,8 @@ func (d *Deployer) SetLogger(logger *slog.Logger) {
} else {
d.logger = logger
}
d.sdkCertmgr.SetLogger(logger)
}
func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
@@ -73,57 +91,45 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
return nil, fmt.Errorf("config `domain` is required")
}
// 上传证书
upres, err := d.sdkCertmgr.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 为存储空间绑定自定义域名
// REF: https://help.aliyun.com/zh/oss/developer-reference/putcname
putCnameReq := &oss.PutCnameRequest{
Bucket: tea.String(d.config.Bucket),
BucketCnameConfiguration: &oss.BucketCnameConfiguration{
putBucketCnameReq := &osssdk.PutCnameRequest{
Cname: &osssdk.PutCnameRequestCname{
Domain: tea.String(d.config.Domain),
CertificateConfiguration: &oss.CertificateConfiguration{
CertificateConfiguration: &osssdk.PutCnameRequestCnameCertificateConfiguration{
CertId: tea.String(upres.ExtendedData["CertIdentifier"].(string)),
Certificate: tea.String(certPEM),
PrivateKey: tea.String(privkeyPEM),
Force: tea.Bool(true),
},
},
}
putCnameResp, err := d.sdkClient.PutCname(ctx, putCnameReq)
d.logger.Debug("sdk request 'oss.PutCname'", slog.Any("request", putCnameReq), slog.Any("response", putCnameResp))
putBucketCnameResp, err := d.sdkClient.PutBucketCnameWithContext(ctx, putBucketCnameReq)
d.logger.Debug("sdk request 'oss.PutBucketCname'", slog.String("params.bucket", d.config.Bucket), slog.Any("request", putBucketCnameReq), slog.Any("response", putBucketCnameResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'oss.PutCname': %w", err)
return nil, fmt.Errorf("failed to execute sdk request 'oss.PutBucketCname': %w", err)
}
return &DeployResult{}, nil
}
func createSDKClient(accessKeyId, accessKeySecret, region string) (*oss.Client, error) {
// 接入点一览 https://api.aliyun.com/product/Oss
var endpoint string
switch region {
case "":
endpoint = "oss.aliyuncs.com"
case
"cn-hzjbp",
"cn-hzjbp-a",
"cn-hzjbp-b":
endpoint = "oss-cn-hzjbp-a-internal.aliyuncs.com"
case
"cn-shanghai-finance-1",
"cn-shenzhen-finance-1",
"cn-beijing-finance-1",
"cn-north-2-gov-1":
endpoint = fmt.Sprintf("oss-%s-internal.aliyuncs.com", region)
default:
endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region)
func createSDKClient(accessKeyId, accessKeySecret, region, bucket string) (*osssdk.Client, error) {
client, err := osssdk.NewClient("",
osssdk.WithAkSk(accessKeyId, accessKeySecret),
osssdk.WithRegion(region),
osssdk.WithBucket(bucket),
)
if err != nil {
return nil, err
}
provider := credentials.NewStaticCredentialsProvider(accessKeyId, accessKeySecret)
config := oss.LoadDefaultConfig().
WithCredentialsProvider(provider).
WithEndpoint(endpoint)
if region != "" {
config = config.WithRegion(region)
}
client := oss.NewClient(config)
return client, nil
}
@@ -105,7 +105,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
CertificateChain: certPEM,
PrivateKey: privkeyPEM,
}
putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomainWithContext(ctx, d.config.Bucket, putBucketCustomDomainReq)
putBucketCustomDomainResp, err := d.sdkClient.PutBucketCustomDomainWithContext(ctx, putBucketCustomDomainReq)
d.logger.Debug("sdk request 'obs.PutBucketCustomDomain'", slog.String("params.bucket", d.config.Bucket), slog.String("params.customdomain", d.config.Domain), slog.Any("request", putBucketCustomDomainReq), slog.Any("response", putBucketCustomDomainResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'obs.PutBucketCustomDomain': %w", err)
@@ -0,0 +1,51 @@
package oss
import (
"context"
"encoding/xml"
"fmt"
"net/http"
)
type PutCnameRequest struct {
XMLName xml.Name `json:"-" xml:"BucketCnameConfiguration"`
Cname *PutCnameRequestCname `json:",omitempty" xml:"Cname,omitempty"`
}
type PutCnameRequestCname struct {
Domain *string `json:",omitempty" xml:"Domain,omitempty"`
CertificateConfiguration *PutCnameRequestCnameCertificateConfiguration `json:",omitempty" xml:"CertificateConfiguration,omitempty"`
}
type PutCnameRequestCnameCertificateConfiguration struct {
CertId *string `json:",omitempty" xml:"CertId,omitempty"`
Certificate *string `json:",omitempty" xml:"Certificate,omitempty"`
PrivateKey *string `json:",omitempty" xml:"PrivateKey,omitempty"`
PreviousCertId *string `json:",omitempty" xml:"PreviousCertId,omitempty"`
Force *bool `json:",omitempty" xml:"Force,omitempty"`
DeleteCertificate *bool `json:",omitempty" xml:"DeleteCertificate,omitempty"`
}
type PutCnameResponse struct {
sdkResponseBase
}
func (c *Client) PutBucketCname(req *PutCnameRequest) (*PutCnameResponse, error) {
return c.PutBucketCnameWithContext(context.Background(), req)
}
func (c *Client) PutBucketCnameWithContext(ctx context.Context, req *PutCnameRequest) (*PutCnameResponse, error) {
httpreq, err := c.newRequest(http.MethodPost, "/?cname&comp=add", fmt.Sprintf("/%s/", c.bucket), req)
if err != nil {
return nil, err
} else {
httpreq.SetContext(ctx)
}
result := &PutCnameResponse{}
if _, err := c.doRequestWithResult(httpreq, result); err != nil {
return result, err
}
return result, nil
}
+296
View File
@@ -0,0 +1,296 @@
package oss
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/xml"
"fmt"
"hash"
"net/http"
"net/url"
"sort"
"strings"
"time"
"github.com/go-resty/resty/v2"
"github.com/certimate-go/certimate/internal/app"
)
type Client struct {
bucket string
rc *resty.Client
}
func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
opts := &Options{}
for _, fn := range optFns {
fn(opts)
}
if opts.Region == "" {
return nil, fmt.Errorf("sdkerr: unset region")
}
if opts.AccessKeyId == "" {
return nil, fmt.Errorf("sdkerr: unset accessKeyId")
}
if opts.AccessKeySecret == "" {
return nil, fmt.Errorf("sdkerr: unset secretAccessKey")
}
if endpoint == "" {
if opts.Bucket == "" {
endpoint = fmt.Sprintf("https://oss-%s.aliyuncs.com", url.PathEscape(opts.Region))
} else {
endpoint = fmt.Sprintf("https://%s.oss-%s.aliyuncs.com", url.PathEscape(opts.Bucket), url.PathEscape(opts.Region))
}
} else {
if baseUrl, err := url.Parse(endpoint); err != nil {
return nil, fmt.Errorf("sdkerr: invalid endpoint: %w", err)
} else if baseUrl.Scheme == "" {
endpoint = "https://" + endpoint
}
}
restyClient := resty.New().
SetBaseURL(endpoint).
SetHeader("User-Agent", app.AppUserAgent).
SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
// API 签名机制:
// https://help.aliyun.com/zh/oss/developer-reference/recommend-to-use-signature-version-4
// https://www.alibabacloud.com/help/en/oss/developer-reference/recommend-to-use-signature-version-4
method := strings.ToUpper(req.Method)
nowUtc := time.Now().UTC()
headerDateStr := nowUtc.Format(http.TimeFormat)
requestDateStr := nowUtc.Format("20060102T150405Z")
signDateStr := nowUtc.Format("20060102")
requestResStr := req.Header.Get("X-API-Resource")
req.Header.Del("X-API-Resource")
canonicalUrl := escapePath(req.URL.Path)
if canonicalUrl == "" {
canonicalUrl = "/"
}
if canonicalUrl == "/" && requestResStr != "" {
canonicalUrl = escapePath(requestResStr)
}
canonicalQueryStr := ""
if len(req.URL.Query()) > 0 {
query := req.URL.Query()
keys := make([]string, 0, len(query))
for key := range query {
keys = append(keys, key)
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
canonicalQueryStr += "&"
}
value := query.Get(key)
if value == "" {
canonicalQueryStr += escapeQuery(key)
} else {
canonicalQueryStr += escapeQuery(key) + "=" + escapeQuery(value)
}
}
}
canonicalHeaders := ""
additionalHeaders := ""
if len(req.Header) > 0 {
if req.Header.Get("X-OSS-Date") == "" {
req.Header.Set("X-OSS-Date", requestDateStr)
}
if req.Header.Get("X-OSS-Content-SHA256") == "" {
req.Header.Set("X-OSS-Content-SHA256", "UNSIGNED-PAYLOAD")
}
keys := make([]string, 0, len(req.Header))
for key := range req.Header {
key = strings.ToLower(key)
if strings.HasPrefix(key, "x-oss-") {
keys = append(keys, key)
}
if key == "content-type" || key == "content-md5" {
keys = append(keys, key)
}
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
canonicalHeaders += "\n"
}
value := strings.TrimSpace(req.Header.Get(key))
canonicalHeaders += key + ":" + value
}
canonicalHeaders += "\n"
}
hashedPayload := req.Header.Get("X-OSS-Content-SHA256")
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, additionalHeaders, hashedPayload)
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
const signAlgorithmHeader = "OSS4-HMAC-SHA256"
scope := fmt.Sprintf("%s/%s/oss/aliyun_v4_request", signDateStr, opts.Region)
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", signAlgorithmHeader, requestDateStr, scope, canonicalRequestHashHex)
var h hash.Hash
h = hmac.New(sha256.New, []byte("aliyun_v4"+opts.AccessKeySecret))
h.Write([]byte(signDateStr))
kDate := h.Sum(nil)
h = hmac.New(sha256.New, kDate)
h.Write([]byte(opts.Region))
kRegion := h.Sum(nil)
h = hmac.New(sha256.New, kRegion)
h.Write([]byte("oss"))
kService := h.Sum(nil)
h = hmac.New(sha256.New, kService)
h.Write([]byte("aliyun_v4_request"))
kSigning := h.Sum(nil)
h = hmac.New(sha256.New, kSigning)
h.Write([]byte(stringToSign))
signature := strings.ToLower(hex.EncodeToString(h.Sum(nil)))
req.Header.Set("Authorization", fmt.Sprintf("%s Credential=%s/%s, Signature=%s", signAlgorithmHeader, opts.AccessKeyId, scope, signature))
req.Header.Set("Date", headerDateStr)
return nil
})
return &Client{
bucket: opts.Bucket,
rc: restyClient,
}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) *Client {
c.rc.SetTimeout(timeout)
return c
}
func (c *Client) newRequest(method string, path string, resource string, params any) (*resty.Request, error) {
if method == "" {
return nil, fmt.Errorf("sdkerr: unset method")
}
if path == "" {
return nil, fmt.Errorf("sdkerr: unset path")
}
requestUrl, err := url.Parse(path)
if err != nil {
return nil, fmt.Errorf("sdkerr: invalid path: %w", err)
} else if requestUrl.IsAbs() {
return nil, fmt.Errorf("sdkerr: path should be relative")
}
payloadStr := ""
contentType := ""
if params != nil {
// 目前仅支持 XML 请求体,仅适用于非 S3 兼容接口
payloadb, err := xml.Marshal(params)
if err != nil {
return nil, err
}
payloadStr = string(payloadb)
contentType = "application/xml"
}
payloadMd5 := md5.Sum([]byte(payloadStr))
payloadMd5Encoded := base64.StdEncoding.EncodeToString(payloadMd5[:])
req := c.rc.R()
req.Method = method
req.URL = requestUrl.Path
req.QueryParam = requestUrl.Query()
req.SetHeader("Content-MD5", payloadMd5Encoded)
req.SetHeader("Content-Type", contentType)
req.SetHeader("X-API-Resource", resource)
req.SetBody(payloadStr)
return req, nil
}
func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
// WARN:
// PLEASE DO NOT USE `req.SetBody` HERE! USE `newRequest` INSTEAD.
// PLEASE DO NOT USE `req.SetResult` or `req.SetError` HERE! USE `doRequestWithResult` INSTEAD.
resp, err := req.Send()
if err != nil {
return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
} else if resp.IsError() {
return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
}
return resp, nil
}
func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*resty.Response, error) {
if req == nil {
return nil, fmt.Errorf("sdkerr: nil request")
}
resp, err := c.doRequest(req)
if err != nil {
if resp != nil {
xml.Unmarshal(resp.Body(), &res)
}
return resp, err
}
if len(resp.Body()) != 0 {
if err := xml.Unmarshal(resp.Body(), &res); err != nil {
return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
}
}
return resp, nil
}
func escapeQuery(str string) string {
res := url.QueryEscape(str)
res = strings.ReplaceAll(res, "+", "%20")
return res
}
func escapePath(path string) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
noEscape := (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '.' ||
c == '_' ||
c == '~' ||
c == '/'
if noEscape {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}
+29
View File
@@ -0,0 +1,29 @@
package oss
type Options struct {
AccessKeyId string
AccessKeySecret string
Region string
Bucket string
}
type OptionsFunc func(*Options)
func WithAkSk(ak, sk string) OptionsFunc {
return func(o *Options) {
o.AccessKeyId = ak
o.AccessKeySecret = sk
}
}
func WithRegion(region string) OptionsFunc {
return func(o *Options) {
o.Region = region
}
}
func WithBucket(bucket string) OptionsFunc {
return func(o *Options) {
o.Bucket = bucket
}
}
+7
View File
@@ -0,0 +1,7 @@
package oss
type sdkResponse interface{}
type sdkResponseBase struct{}
var _ sdkResponse = (*sdkResponseBase)(nil)
@@ -22,14 +22,11 @@ type PutBucketCustomDomainResponse struct {
sdkResponseBase
}
func (c *Client) PutBucketCustomDomain(bucket string, req *PutBucketCustomDomainRequest) (*PutBucketCustomDomainResponse, error) {
return c.PutBucketCustomDomainWithContext(context.Background(), bucket, req)
func (c *Client) PutBucketCustomDomain(req *PutBucketCustomDomainRequest) (*PutBucketCustomDomainResponse, error) {
return c.PutBucketCustomDomainWithContext(context.Background(), req)
}
func (c *Client) PutBucketCustomDomainWithContext(ctx context.Context, bucket string, req *PutBucketCustomDomainRequest) (*PutBucketCustomDomainResponse, error) {
if bucket == "" {
return nil, fmt.Errorf("sdkerr: bad request: unset bucket")
}
func (c *Client) PutBucketCustomDomainWithContext(ctx context.Context, req *PutBucketCustomDomainRequest) (*PutBucketCustomDomainResponse, error) {
if req.CustomDomain == "" {
return nil, fmt.Errorf("sdkerr: bad request: unset customdomain")
}
+38 -9
View File
@@ -1,6 +1,7 @@
package obs
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
@@ -72,16 +73,16 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
for _, key := range keys {
value := strings.TrimSpace(req.Header.Get(key))
canonicalizedHeaders += fmt.Sprintf("%s:%s", key, url.QueryEscape(value))
canonicalizedHeaders += key + ":" + escapeQuery(value)
canonicalizedHeaders += "\n"
}
}
bucketName := opts.Bucket
objectName := strings.Trim(req.URL.Path, "/")
canonicalizedResource := fmt.Sprintf("/%s/%s", bucketName, objectName)
canonicalizedResources := escapePath(fmt.Sprintf("/%s/%s", bucketName, objectName))
if bucketName == "" && objectName == "" {
canonicalizedResource = "/"
canonicalizedResources = "/"
}
if len(req.URL.Query()) > 0 {
query := req.URL.Query()
@@ -94,16 +95,16 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
for i, key := range keys {
if i == 0 {
canonicalizedResource += "?"
canonicalizedResources += "?"
} else {
canonicalizedResource += "&"
canonicalizedResources += "&"
}
value := query.Get(key)
if value == "" {
canonicalizedResource += key
canonicalizedResources += escapeQuery(key)
} else {
canonicalizedResource += fmt.Sprintf("%s=%s", strings.ToLower(key), url.QueryEscape(value))
canonicalizedResources += escapeQuery(key) + "=" + escapeQuery(value)
}
}
}
@@ -115,8 +116,7 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
contentMd5 := req.Header.Get("Content-MD5")
contentType := req.Header.Get("Content-Type")
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s\n%s%s", method, contentMd5, contentType, dateStr, canonicalizedHeaders, canonicalizedResource)
println("stringToSign:", stringToSign)
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s\n%s%s", method, contentMd5, contentType, dateStr, canonicalizedHeaders, canonicalizedResources)
h := hmac.New(sha1.New, []byte(opts.SecretAccessKey))
h.Write([]byte(stringToSign))
@@ -216,3 +216,32 @@ func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*rest
return resp, nil
}
func escapeQuery(str string) string {
res := url.QueryEscape(str)
res = strings.ReplaceAll(res, "%7E", "~")
res = strings.ReplaceAll(res, "%2F", "/")
res = strings.ReplaceAll(res, "%20", "+")
return res
}
func escapePath(path string) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
noEscape := (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '.' ||
c == '_' ||
c == '~' ||
c == '/'
if noEscape {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}
+36 -8
View File
@@ -1,6 +1,7 @@
package tos
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
@@ -69,7 +70,7 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
requestDateStr := nowUtc.Format("20060102T150405Z")
credentialDateStr := nowUtc.Format("20060102")
canonicalUrl := req.URL.Path
canonicalUrl := escapePath(req.URL.Path)
if canonicalUrl == "" {
canonicalUrl = "/"
}
@@ -90,7 +91,7 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
}
value := query.Get(key)
canonicalQueryStr += fmt.Sprintf("%s=%s", url.QueryEscape(key), url.QueryEscape(value))
canonicalQueryStr += escapeQuery(key) + "=" + escapeQuery(value)
}
}
@@ -123,20 +124,20 @@ func NewClient(endpoint string, optFns ...OptionsFunc) (*Client, error) {
}
value := strings.TrimSpace(req.Header.Get(key))
canonicalHeaders += fmt.Sprintf("%s:%s", key, value)
canonicalHeaders += key + ":" + value
signedHeaders += key
}
canonicalHeaders += "\n"
}
payloadSha256Str := req.Header.Get("X-TOS-Content-SHA256")
if payloadSha256Str == "" {
payloadSha256 := sha256.Sum256([]byte{})
payloadSha256Str = strings.ToLower(hex.EncodeToString(payloadSha256[:]))
hashedPayload := req.Header.Get("X-TOS-Content-SHA256")
if hashedPayload == "" {
temp := sha256.Sum256([]byte{})
hashedPayload = strings.ToLower(hex.EncodeToString(temp[:]))
}
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, signedHeaders, payloadSha256Str)
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", method, canonicalUrl, canonicalQueryStr, canonicalHeaders, signedHeaders, hashedPayload)
canonicalRequestHash := sha256.Sum256([]byte(canonicalRequest))
canonicalRequestHashHex := strings.ToLower(hex.EncodeToString(canonicalRequestHash[:]))
@@ -256,3 +257,30 @@ func (c *Client) doRequestWithResult(req *resty.Request, res sdkResponse) (*rest
return resp, nil
}
func escapeQuery(str string) string {
res := url.QueryEscape(str)
res = strings.ReplaceAll(res, "+", "%20")
return res
}
func escapePath(path string) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
noEscape := (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' ||
c == '.' ||
c == '_' ||
c == '~' ||
c == '/'
if noEscape {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}