[chore]: Bump github.com/minio/minio-go/v7 from 7.0.85 to 7.0.89 (#3977)

Bumps [github.com/minio/minio-go/v7](https://github.com/minio/minio-go) from 7.0.85 to 7.0.89.
- [Release notes](https://github.com/minio/minio-go/releases)
- [Commits](https://github.com/minio/minio-go/compare/v7.0.85...v7.0.89)

---
updated-dependencies:
- dependency-name: github.com/minio/minio-go/v7
  dependency-version: 7.0.89
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
dependabot[bot]
2025-04-07 11:05:51 +01:00
committed by GitHub
parent e0ea77b730
commit bce643286c
46 changed files with 1337 additions and 371 deletions

View File

@ -1,27 +1,72 @@
linters-settings:
misspell:
locale: US
version: "2"
linters:
disable-all: true
enable:
- typecheck
- goimports
- misspell
- revive
- durationcheck
- gocritic
- gomodguard
- govet
- ineffassign
- gosimple
- misspell
- revive
- staticcheck
- unconvert
- unused
- gocritic
- usetesting
- whitespace
settings:
misspell:
locale: US
staticcheck:
checks:
- all
- -SA1008
- -SA1019
- -SA4000
- -SA9004
- -ST1000
- -ST1005
- -ST1016
- -ST1021
- -ST1020
- -U1000
exclusions:
generated: lax
rules:
- path: (.+)\.go$
text: "empty-block:"
- path: (.+)\.go$
text: "unused-parameter:"
- path: (.+)\.go$
text: "dot-imports:"
- path: (.+)\.go$
text: "singleCaseSwitch: should rewrite switch statement to if statement"
- path: (.+)\.go$
text: "unlambda: replace"
- path: (.+)\.go$
text: "captLocal:"
- path: (.+)\.go$
text: "should have a package comment"
- path: (.+)\.go$
text: "ifElseChain:"
- path: (.+)\.go$
text: "elseif:"
- path: (.+)\.go$
text: "Error return value of"
- path: (.+)\.go$
text: "unnecessary conversion"
- path: (.+)\.go$
text: "Error return value is not checked"
issues:
exclude-use-default: false
exclude:
# todo fix these when we get enough time.
- "singleCaseSwitch: should rewrite switch statement to if statement"
- "unlambda: replace"
- "captLocal:"
- "ifElseChain:"
- "elseif:"
- "should have a package comment"
max-issues-per-linter: 100
max-same-issues: 100
formatters:
enable:
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

View File

@ -251,7 +251,6 @@ func (c *Client) ListenBucketNotification(ctx context.Context, bucketName, prefi
// Close current connection before looping further.
closeResponse(resp)
}
}(notificationInfoCh)

View File

@ -90,6 +90,7 @@ type BucketVersioningConfiguration struct {
// Requires versioning to be enabled
ExcludedPrefixes []ExcludedPrefix `xml:",omitempty"`
ExcludeFolders bool `xml:",omitempty"`
PurgeOnDelete string `xml:",omitempty"`
}
// Various supported states

View File

@ -30,6 +30,7 @@ import (
"github.com/google/uuid"
"github.com/minio/minio-go/v7/pkg/encrypt"
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio-go/v7/pkg/tags"
)
// CopyDestOptions represents options specified by user for CopyObject/ComposeObject APIs
@ -98,8 +99,8 @@ func (opts CopyDestOptions) Marshal(header http.Header) {
const replaceDirective = "REPLACE"
if opts.ReplaceTags {
header.Set(amzTaggingHeaderDirective, replaceDirective)
if tags := s3utils.TagEncode(opts.UserTags); tags != "" {
header.Set(amzTaggingHeader, tags)
if tags, _ := tags.NewTags(opts.UserTags, true); tags != nil {
header.Set(amzTaggingHeader, tags.String())
}
}
@ -236,7 +237,9 @@ func (c *Client) copyObjectDo(ctx context.Context, srcBucket, srcObject, destBuc
}
if len(dstOpts.UserTags) != 0 {
headers.Set(amzTaggingHeader, s3utils.TagEncode(dstOpts.UserTags))
if tags, _ := tags.NewTags(dstOpts.UserTags, true); tags != nil {
headers.Set(amzTaggingHeader, tags.String())
}
}
reqMetadata := requestMetadata{

View File

@ -212,6 +212,8 @@ type ObjectInfo struct {
// not to be confused with `Expires` HTTP header.
Expiration time.Time
ExpirationRuleID string
// NumVersions is the number of versions of the object.
NumVersions int
Restore *RestoreInfo

View File

@ -135,16 +135,16 @@ func getAmzGrantACL(aCPolicy *accessControlPolicy) map[string][]string {
res := map[string][]string{}
for _, g := range grants {
switch {
case g.Permission == "READ":
switch g.Permission {
case "READ":
res["X-Amz-Grant-Read"] = append(res["X-Amz-Grant-Read"], "id="+g.Grantee.ID)
case g.Permission == "WRITE":
case "WRITE":
res["X-Amz-Grant-Write"] = append(res["X-Amz-Grant-Write"], "id="+g.Grantee.ID)
case g.Permission == "READ_ACP":
case "READ_ACP":
res["X-Amz-Grant-Read-Acp"] = append(res["X-Amz-Grant-Read-Acp"], "id="+g.Grantee.ID)
case g.Permission == "WRITE_ACP":
case "WRITE_ACP":
res["X-Amz-Grant-Write-Acp"] = append(res["X-Amz-Grant-Write-Acp"], "id="+g.Grantee.ID)
case g.Permission == "FULL_CONTROL":
case "FULL_CONTROL":
res["X-Amz-Grant-Full-Control"] = append(res["X-Amz-Grant-Full-Control"], "id="+g.Grantee.ID)
}
}

View File

@ -22,6 +22,7 @@ import (
"fmt"
"net/http"
"net/url"
"slices"
"time"
"github.com/minio/minio-go/v7/pkg/s3utils"
@ -421,20 +422,17 @@ func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts
var (
keyMarker = ""
versionIDMarker = ""
preName = ""
preKey = ""
perVersions []Version
numVersions int
)
for {
// Get list of objects a maximum of 1000 per request.
result, err := c.listObjectVersionsQuery(ctx, bucketName, opts, keyMarker, versionIDMarker, delimiter)
if err != nil {
sendObjectInfo(ObjectInfo{
Err: err,
})
return
send := func(vers []Version) {
if opts.WithVersions && opts.ReverseVersions {
slices.Reverse(vers)
numVersions = len(vers)
}
// If contents are available loop through and send over channel.
for _, version := range result.Versions {
for _, version := range vers {
info := ObjectInfo{
ETag: trimEtag(version.ETag),
Key: version.Key,
@ -448,6 +446,7 @@ func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts
UserTags: version.UserTags,
UserMetadata: version.UserMetadata,
Internal: version.Internal,
NumVersions: numVersions,
}
select {
// Send object version info.
@ -457,6 +456,38 @@ func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts
return
}
}
}
for {
// Get list of objects a maximum of 1000 per request.
result, err := c.listObjectVersionsQuery(ctx, bucketName, opts, keyMarker, versionIDMarker, delimiter)
if err != nil {
sendObjectInfo(ObjectInfo{
Err: err,
})
return
}
if opts.WithVersions && opts.ReverseVersions {
for _, version := range result.Versions {
if preName == "" {
preName = result.Name
preKey = version.Key
}
if result.Name == preName && preKey == version.Key {
// If the current name is same as previous name,
// we need to append the version to the previous version.
perVersions = append(perVersions, version)
continue
}
// Send the file versions.
send(perVersions)
perVersions = perVersions[:0]
perVersions = append(perVersions, version)
preName = result.Name
preKey = version.Key
}
} else {
send(result.Versions)
}
// Send all common prefixes if any.
// NOTE: prefixes are only present if the request is delimited.
@ -480,8 +511,17 @@ func (c *Client) listObjectVersions(ctx context.Context, bucketName string, opts
versionIDMarker = result.NextVersionIDMarker
}
// If context is canceled, return here.
if contextCanceled(ctx) {
return
}
// Listing ends result is not truncated, return right here.
if !result.IsTruncated {
// sent the lasted file with versions
if opts.ReverseVersions && len(perVersions) > 0 {
send(perVersions)
}
return
}
}
@ -683,6 +723,8 @@ func (c *Client) listObjectsQuery(ctx context.Context, bucketName, objectPrefix,
// ListObjectsOptions holds all options of a list object request
type ListObjectsOptions struct {
// ReverseVersions - reverse the order of the object versions
ReverseVersions bool
// Include objects versions in the listing
WithVersions bool
// Include objects metadata in the listing

View File

@ -350,7 +350,6 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b
// Part number always starts with '1'.
var partNumber int
for partNumber = 1; partNumber <= totalPartsCount; partNumber++ {
// Proceed to upload the part.
if partNumber == totalPartsCount {
partSize = lastPartSize

View File

@ -30,6 +30,7 @@ import (
"github.com/minio/minio-go/v7/pkg/encrypt"
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio-go/v7/pkg/tags"
"golang.org/x/net/http/httpguts"
)
@ -229,7 +230,9 @@ func (opts PutObjectOptions) Header() (header http.Header) {
}
if len(opts.UserTags) != 0 {
header.Set(amzTaggingHeader, s3utils.TagEncode(opts.UserTags))
if tags, _ := tags.NewTags(opts.UserTags, true); tags != nil {
header.Set(amzTaggingHeader, tags.String())
}
}
for k, v := range opts.UserMetadata {

View File

@ -392,10 +392,7 @@ func (c *Client) removeObjects(ctx context.Context, bucketName string, objectsCh
defer close(resultCh)
// Loop over entries by 1000 and call MultiDelete requests
for {
if finish {
break
}
for !finish {
count := 0
var batch []ObjectInfo

View File

@ -194,7 +194,6 @@ func (l *ListVersionsResult) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) (e
default:
return errors.New("unrecognized option:" + tagName)
}
}
}
return nil

View File

@ -609,7 +609,6 @@ func (s *SelectResults) start(pipeWriter *io.PipeWriter) {
closeResponse(s.resp)
return
}
}
}()
}
@ -669,7 +668,6 @@ func extractHeader(body io.Reader, myHeaders http.Header) error {
}
myHeaders.Set(headerTypeName, headerValueName)
}
return nil
}

View File

@ -155,7 +155,7 @@ type Options struct {
// Global constants.
const (
libraryName = "minio-go"
libraryVersion = "v7.0.85"
libraryVersion = "v7.0.89"
)
// User Agent should always following the below style.
@ -598,7 +598,7 @@ func (c *Client) do(req *http.Request) (resp *http.Response, err error) {
// If trace is enabled, dump http request and response,
// except when the traceErrorsOnly enabled and the response's status code is ok
if c.isTraceEnabled && !(c.traceErrorsOnly && resp.StatusCode == http.StatusOK) {
if c.isTraceEnabled && (!c.traceErrorsOnly || resp.StatusCode != http.StatusOK) {
err = c.dumpHTTP(req, resp)
if err != nil {
return nil, err

View File

@ -30,6 +30,8 @@ import (
"math/bits"
"net/http"
"sort"
"github.com/minio/crc64nvme"
)
// ChecksumType contains information about the checksum type.
@ -152,9 +154,6 @@ func (c ChecksumType) RawByteLen() int {
const crc64NVMEPolynomial = 0xad93d23594c93659
// crc64 uses reversed polynomials.
var crc64Table = crc64.MakeTable(bits.Reverse64(crc64NVMEPolynomial))
// Hasher returns a hasher corresponding to the checksum type.
// Returns nil if no checksum.
func (c ChecksumType) Hasher() hash.Hash {
@ -168,7 +167,7 @@ func (c ChecksumType) Hasher() hash.Hash {
case ChecksumSHA256:
return sha256.New()
case ChecksumCRC64NVME:
return crc64.New(crc64Table)
return crc64nvme.New()
}
return nil
}

View File

@ -104,6 +104,8 @@ type STSAssumeRoleOptions struct {
RoleARN string
RoleSessionName string
ExternalID string
TokenRevokeType string // Optional, used for token revokation (MinIO only extension)
}
// NewSTSAssumeRole returns a pointer to a new
@ -161,6 +163,9 @@ func getAssumeRoleCredentials(clnt *http.Client, endpoint string, opts STSAssume
if opts.ExternalID != "" {
v.Set("ExternalId", opts.ExternalID)
}
if opts.TokenRevokeType != "" {
v.Set("TokenRevokeType", opts.TokenRevokeType)
}
u, err := url.Parse(endpoint)
if err != nil {

View File

@ -69,6 +69,9 @@ type CustomTokenIdentity struct {
// RequestedExpiry is to set the validity of the generated credentials
// (this value bounded by server).
RequestedExpiry time.Duration
// Optional, used for token revokation
TokenRevokeType string
}
// RetrieveWithCredContext with Retrieve optionally cred context
@ -98,6 +101,9 @@ func (c *CustomTokenIdentity) RetrieveWithCredContext(cc *CredContext) (value Va
if c.RequestedExpiry != 0 {
v.Set("DurationSeconds", fmt.Sprintf("%d", int(c.RequestedExpiry.Seconds())))
}
if c.TokenRevokeType != "" {
v.Set("TokenRevokeType", c.TokenRevokeType)
}
u.RawQuery = v.Encode()

View File

@ -73,6 +73,9 @@ type LDAPIdentity struct {
// RequestedExpiry is the configured expiry duration for credentials
// requested from LDAP.
RequestedExpiry time.Duration
// Optional, used for token revokation
TokenRevokeType string
}
// NewLDAPIdentity returns new credentials object that uses LDAP
@ -152,6 +155,9 @@ func (k *LDAPIdentity) RetrieveWithCredContext(cc *CredContext) (value Value, er
if k.RequestedExpiry != 0 {
v.Set("DurationSeconds", fmt.Sprintf("%d", int(k.RequestedExpiry.Seconds())))
}
if k.TokenRevokeType != "" {
v.Set("TokenRevokeType", k.TokenRevokeType)
}
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(v.Encode()))
if err != nil {

View File

@ -80,6 +80,9 @@ type STSCertificateIdentity struct {
// Certificate is the client certificate that is used for
// STS authentication.
Certificate tls.Certificate
// Optional, used for token revokation
TokenRevokeType string
}
// NewSTSCertificateIdentity returns a STSCertificateIdentity that authenticates
@ -122,6 +125,9 @@ func (i *STSCertificateIdentity) RetrieveWithCredContext(cc *CredContext) (Value
queryValues := url.Values{}
queryValues.Set("Action", "AssumeRoleWithCertificate")
queryValues.Set("Version", STSVersion)
if i.TokenRevokeType != "" {
queryValues.Set("TokenRevokeType", i.TokenRevokeType)
}
endpointURL.RawQuery = queryValues.Encode()
req, err := http.NewRequest(http.MethodPost, endpointURL.String(), nil)

View File

@ -93,6 +93,9 @@ type STSWebIdentity struct {
// roleSessionName is the identifier for the assumed role session.
roleSessionName string
// Optional, used for token revokation
TokenRevokeType string
}
// NewSTSWebIdentity returns a pointer to a new
@ -135,7 +138,7 @@ func WithPolicy(policy string) func(*STSWebIdentity) {
}
func getWebIdentityCredentials(clnt *http.Client, endpoint, roleARN, roleSessionName string, policy string,
getWebIDTokenExpiry func() (*WebIdentityToken, error),
getWebIDTokenExpiry func() (*WebIdentityToken, error), tokenRevokeType string,
) (AssumeRoleWithWebIdentityResponse, error) {
idToken, err := getWebIDTokenExpiry()
if err != nil {
@ -168,6 +171,9 @@ func getWebIdentityCredentials(clnt *http.Client, endpoint, roleARN, roleSession
v.Set("Policy", policy)
}
v.Set("Version", STSVersion)
if tokenRevokeType != "" {
v.Set("TokenRevokeType", tokenRevokeType)
}
u, err := url.Parse(endpoint)
if err != nil {
@ -236,7 +242,7 @@ func (m *STSWebIdentity) RetrieveWithCredContext(cc *CredContext) (Value, error)
return Value{}, errors.New("STS endpoint unknown")
}
a, err := getWebIdentityCredentials(client, stsEndpoint, m.RoleARN, m.roleSessionName, m.Policy, m.GetWebIDTokenExpiry)
a, err := getWebIdentityCredentials(client, stsEndpoint, m.RoleARN, m.roleSessionName, m.Policy, m.GetWebIDTokenExpiry, m.TokenRevokeType)
if err != nil {
return Value{}, err
}

View File

@ -192,7 +192,7 @@ func (t Transition) IsDaysNull() bool {
// IsDateNull returns true if date field is null
func (t Transition) IsDateNull() bool {
return t.Date.Time.IsZero()
return t.Date.IsZero()
}
// IsNull returns true if no storage-class is set.
@ -323,7 +323,7 @@ type ExpirationDate struct {
// MarshalXML encodes expiration date if it is non-zero and encodes
// empty string otherwise
func (eDate ExpirationDate) MarshalXML(e *xml.Encoder, startElement xml.StartElement) error {
if eDate.Time.IsZero() {
if eDate.IsZero() {
return nil
}
return e.EncodeElement(eDate.Format(time.RFC3339), startElement)
@ -392,7 +392,7 @@ func (e Expiration) IsDaysNull() bool {
// IsDateNull returns true if date field is null
func (e Expiration) IsDateNull() bool {
return e.Date.Time.IsZero()
return e.Date.IsZero()
}
// IsDeleteMarkerExpirationEnabled returns true if the auto-expiration of delete marker is enabled

View File

@ -283,7 +283,6 @@ func (b *Configuration) AddTopic(topicConfig Config) bool {
for _, n := range b.TopicConfigs {
// If new config matches existing one
if n.Topic == newTopicConfig.Arn.String() && newTopicConfig.Filter == n.Filter {
existingConfig := set.NewStringSet()
for _, v := range n.Events {
existingConfig.Add(string(v))
@ -308,7 +307,6 @@ func (b *Configuration) AddQueue(queueConfig Config) bool {
newQueueConfig := QueueConfig{Config: queueConfig, Queue: queueConfig.Arn.String()}
for _, n := range b.QueueConfigs {
if n.Queue == newQueueConfig.Arn.String() && newQueueConfig.Filter == n.Filter {
existingConfig := set.NewStringSet()
for _, v := range n.Events {
existingConfig.Add(string(v))
@ -333,7 +331,6 @@ func (b *Configuration) AddLambda(lambdaConfig Config) bool {
newLambdaConfig := LambdaConfig{Config: lambdaConfig, Lambda: lambdaConfig.Arn.String()}
for _, n := range b.LambdaConfigs {
if n.Lambda == newLambdaConfig.Arn.String() && newLambdaConfig.Filter == n.Filter {
existingConfig := set.NewStringSet()
for _, v := range n.Events {
existingConfig.Add(string(v))
@ -372,7 +369,7 @@ func (b *Configuration) RemoveTopicByArnEventsPrefixSuffix(arn Arn, events []Eve
removeIndex := -1
for i, v := range b.TopicConfigs {
// if it matches events and filters, mark the index for deletion
if v.Topic == arn.String() && v.Config.Equal(events, prefix, suffix) {
if v.Topic == arn.String() && v.Equal(events, prefix, suffix) {
removeIndex = i
break // since we have at most one matching config
}
@ -400,7 +397,7 @@ func (b *Configuration) RemoveQueueByArnEventsPrefixSuffix(arn Arn, events []Eve
removeIndex := -1
for i, v := range b.QueueConfigs {
// if it matches events and filters, mark the index for deletion
if v.Queue == arn.String() && v.Config.Equal(events, prefix, suffix) {
if v.Queue == arn.String() && v.Equal(events, prefix, suffix) {
removeIndex = i
break // since we have at most one matching config
}
@ -428,7 +425,7 @@ func (b *Configuration) RemoveLambdaByArnEventsPrefixSuffix(arn Arn, events []Ev
removeIndex := -1
for i, v := range b.LambdaConfigs {
// if it matches events and filters, mark the index for deletion
if v.Lambda == arn.String() && v.Config.Equal(events, prefix, suffix) {
if v.Lambda == arn.String() && v.Equal(events, prefix, suffix) {
removeIndex = i
break // since we have at most one matching config
}

View File

@ -868,8 +868,20 @@ type ReplQNodeStats struct {
XferStats map[MetricName]XferStats `json:"transferSummary"`
TgtXferStats map[string]map[MetricName]XferStats `json:"tgtTransferStats"`
QStats InQueueMetric `json:"queueStats"`
MRFStats ReplMRFStats `json:"mrfStats"`
QStats InQueueMetric `json:"queueStats"`
MRFStats ReplMRFStats `json:"mrfStats"`
Retries CounterSummary `json:"retries"`
Errors CounterSummary `json:"errors"`
}
// CounterSummary denotes the stats counter summary
type CounterSummary struct {
// Counted last 1hr
Last1hr uint64 `json:"last1hr"`
// Counted last 1m
Last1m uint64 `json:"last1m"`
// Total counted since uptime
Total uint64 `json:"total"`
}
// ReplQueueStats holds stats for replication queue across nodes
@ -914,8 +926,10 @@ type ReplQStats struct {
XferStats map[MetricName]XferStats `json:"xferStats"`
TgtXferStats map[string]map[MetricName]XferStats `json:"tgtXferStats"`
QStats InQueueMetric `json:"qStats"`
MRFStats ReplMRFStats `json:"mrfStats"`
QStats InQueueMetric `json:"qStats"`
MRFStats ReplMRFStats `json:"mrfStats"`
Retries CounterSummary `json:"retries"`
Errors CounterSummary `json:"errors"`
}
// QStats returns cluster level stats for objects in replication queue
@ -958,6 +972,12 @@ func (q ReplQueueStats) QStats() (r ReplQStats) {
r.MRFStats.LastFailedCount += node.MRFStats.LastFailedCount
r.MRFStats.TotalDroppedCount += node.MRFStats.TotalDroppedCount
r.MRFStats.TotalDroppedBytes += node.MRFStats.TotalDroppedBytes
r.Retries.Last1hr += node.Retries.Last1hr
r.Retries.Last1m += node.Retries.Last1m
r.Retries.Total += node.Retries.Total
r.Errors.Last1hr += node.Errors.Last1hr
r.Errors.Last1m += node.Errors.Last1m
r.Errors.Total += node.Errors.Total
r.Uptime += node.Uptime
}
if len(q.Nodes) > 0 {
@ -968,7 +988,21 @@ func (q ReplQueueStats) QStats() (r ReplQStats) {
// MetricsV2 represents replication metrics for a bucket.
type MetricsV2 struct {
Uptime int64 `json:"uptime"`
CurrentStats Metrics `json:"currStats"`
QueueStats ReplQueueStats `json:"queueStats"`
Uptime int64 `json:"uptime"`
CurrentStats Metrics `json:"currStats"`
QueueStats ReplQueueStats `json:"queueStats"`
DowntimeInfo map[string]DowntimeInfo `json:"downtimeInfo"`
}
// DowntimeInfo represents the downtime info
type DowntimeInfo struct {
Duration Stat `json:"duration"`
Count Stat `json:"count"`
}
// Stat represents the aggregates
type Stat struct {
Total int64 `json:"total"`
Avg int64 `json:"avg"`
Max int64 `json:"max"`
}

View File

@ -218,7 +218,7 @@ func IsAmazonPrivateLinkEndpoint(endpointURL url.URL) bool {
if endpointURL == sentinelURL {
return false
}
return amazonS3HostPrivateLink.MatchString(endpointURL.Host)
return amazonS3HostPrivateLink.MatchString(endpointURL.Hostname())
}
// IsGoogleEndpoint - Match if it is exactly Google cloud storage endpoint.
@ -261,44 +261,6 @@ func QueryEncode(v url.Values) string {
return buf.String()
}
// TagDecode - decodes canonical tag into map of key and value.
func TagDecode(ctag string) map[string]string {
if ctag == "" {
return map[string]string{}
}
tags := strings.Split(ctag, "&")
tagMap := make(map[string]string, len(tags))
var err error
for _, tag := range tags {
kvs := strings.SplitN(tag, "=", 2)
if len(kvs) == 0 {
return map[string]string{}
}
if len(kvs) == 1 {
return map[string]string{}
}
tagMap[kvs[0]], err = url.PathUnescape(kvs[1])
if err != nil {
continue
}
}
return tagMap
}
// TagEncode - encodes tag values in their URL encoded form. In
// addition to the percent encoding performed by urlEncodePath() used
// here, it also percent encodes '/' (forward slash)
func TagEncode(tags map[string]string) string {
if tags == nil {
return ""
}
values := url.Values{}
for k, v := range tags {
values[k] = []string{v}
}
return QueryEncode(values)
}
// if object matches reserved string, no need to encode them
var reservedObjectNames = regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")

View File

@ -212,7 +212,6 @@ func (s *StreamingUSReader) Read(buf []byte) (int, error) {
}
return 0, err
}
}
}
return s.buf.Read(buf)

View File

@ -387,7 +387,6 @@ func (s *StreamingReader) Read(buf []byte) (int, error) {
}
return 0, err
}
}
}
return s.buf.Read(buf)

View File

@ -148,7 +148,7 @@ func SignV2(req http.Request, accessKeyID, secretAccessKey string, virtualHost b
// Prepare auth header.
authHeader := new(bytes.Buffer)
authHeader.WriteString(fmt.Sprintf("%s %s:", signV2Algorithm, accessKeyID))
fmt.Fprintf(authHeader, "%s %s:", signV2Algorithm, accessKeyID)
encoder := base64.NewEncoder(base64.StdEncoding, authHeader)
encoder.Write(hm.Sum(nil))
encoder.Close()

View File

@ -128,8 +128,8 @@ func getCanonicalHeaders(req http.Request, ignoredHeaders map[string]bool) strin
for _, k := range headers {
buf.WriteString(k)
buf.WriteByte(':')
switch {
case k == "host":
switch k {
case "host":
buf.WriteString(getHostAddr(&req))
buf.WriteByte('\n')
default:

View File

@ -41,6 +41,7 @@ import (
md5simd "github.com/minio/md5-simd"
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio-go/v7/pkg/tags"
)
func trimEtag(etag string) string {
@ -322,7 +323,13 @@ func ToObjectInfo(bucketName, objectName string, h http.Header) (ObjectInfo, err
userMetadata[strings.TrimPrefix(k, "X-Amz-Meta-")] = v[0]
}
}
userTags := s3utils.TagDecode(h.Get(amzTaggingHeader))
userTags, err := tags.ParseObjectTags(h.Get(amzTaggingHeader))
if err != nil {
return ObjectInfo{}, ErrorResponse{
Code: "InternalError",
}
}
var tagCount int
if count := h.Get(amzTaggingCount); count != "" {
@ -373,7 +380,7 @@ func ToObjectInfo(bucketName, objectName string, h http.Header) (ObjectInfo, err
// which are not part of object metadata.
Metadata: metadata,
UserMetadata: userMetadata,
UserTags: userTags,
UserTags: userTags.ToMap(),
UserTagCount: tagCount,
Restore: restore,