diff --git a/cmd/acl-handlers.go b/cmd/acl-handlers.go
index 1a5f8dc63..8eb458000 100644
--- a/cmd/acl-handlers.go
+++ b/cmd/acl-handlers.go
@@ -20,7 +20,6 @@ import (
"encoding/xml"
"io"
"net/http"
- "net/url"
"github.com/gorilla/mux"
xhttp "github.com/minio/minio/cmd/http"
@@ -180,7 +179,7 @@ func (api objectAPIHandlers) PutObjectACLHandler(w http.ResponseWriter, r *http.
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -244,7 +243,7 @@ func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
diff --git a/cmd/api-resources.go b/cmd/api-resources.go
index d3f1eaa92..74d3eeff8 100644
--- a/cmd/api-resources.go
+++ b/cmd/api-resources.go
@@ -36,8 +36,19 @@ func getListObjectsV1Args(values url.Values) (prefix, marker, delimiter string,
maxkeys = maxObjectList
}
- prefix = values.Get("prefix")
- marker = values.Get("marker")
+ var err error
+ prefix, err = unescapePath(values.Get("prefix"))
+ if err != nil {
+ errCode = ErrInvalidRequest
+ return
+ }
+
+ marker, err = unescapePath(values.Get("marker"))
+ if err != nil {
+ errCode = ErrInvalidRequest
+ return
+ }
+
delimiter = values.Get("delimiter")
encodingType = values.Get("encoding-type")
return
@@ -56,8 +67,19 @@ func getListBucketObjectVersionsArgs(values url.Values) (prefix, marker, delimit
maxkeys = maxObjectList
}
- prefix = values.Get("prefix")
- marker = values.Get("key-marker")
+ var err error
+ prefix, err = unescapePath(values.Get("prefix"))
+ if err != nil {
+ errCode = ErrInvalidRequest
+ return
+ }
+
+ marker, err = unescapePath(values.Get("key-marker"))
+ if err != nil {
+ errCode = ErrInvalidRequest
+ return
+ }
+
delimiter = values.Get("delimiter")
encodingType = values.Get("encoding-type")
versionIDMarker = values.Get("version-id-marker")
@@ -86,8 +108,19 @@ func getListObjectsV2Args(values url.Values) (prefix, token, startAfter, delimit
maxkeys = maxObjectList
}
- prefix = values.Get("prefix")
- startAfter = values.Get("start-after")
+ var err error
+ prefix, err = unescapePath(values.Get("prefix"))
+ if err != nil {
+ errCode = ErrInvalidRequest
+ return
+ }
+
+ startAfter, err = unescapePath(values.Get("start-after"))
+ if err != nil {
+ errCode = ErrInvalidRequest
+ return
+ }
+
delimiter = values.Get("delimiter")
fetchOwner = values.Get("fetch-owner") == "true"
encodingType = values.Get("encoding-type")
@@ -117,8 +150,19 @@ func getBucketMultipartResources(values url.Values) (prefix, keyMarker, uploadID
maxUploads = maxUploadsList
}
- prefix = values.Get("prefix")
- keyMarker = values.Get("key-marker")
+ var err error
+ prefix, err = unescapePath(values.Get("prefix"))
+ if err != nil {
+ errCode = ErrInvalidRequest
+ return
+ }
+
+ keyMarker, err = unescapePath(values.Get("key-marker"))
+ if err != nil {
+ errCode = ErrInvalidRequest
+ return
+ }
+
uploadIDMarker = values.Get("upload-id-marker")
delimiter = values.Get("delimiter")
encodingType = values.Get("encoding-type")
diff --git a/cmd/api-response.go b/cmd/api-response.go
index 09a83b50c..12f318048 100644
--- a/cmd/api-response.go
+++ b/cmd/api-response.go
@@ -416,9 +416,11 @@ func getObjectLocation(r *http.Request, domains []string, bucket, object string)
func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
listbuckets := make([]Bucket, 0, len(buckets))
var data = ListBucketsResponse{}
- var owner = Owner{}
+ var owner = Owner{
+ ID: globalMinioDefaultOwnerID,
+ DisplayName: "minio",
+ }
- owner.ID = globalMinioDefaultOwnerID
for _, bucket := range buckets {
var listbucket = Bucket{}
listbucket.Name = bucket.Name
@@ -435,10 +437,12 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
// generates an ListBucketVersions response for the said bucket with other enumerated options.
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
versions := make([]ObjectVersion, 0, len(resp.Objects))
- var owner = Owner{}
+ var owner = Owner{
+ ID: globalMinioDefaultOwnerID,
+ DisplayName: "minio",
+ }
var data = ListVersionsResponse{}
- owner.ID = globalMinioDefaultOwnerID
for _, object := range resp.Objects {
var content = ObjectVersion{}
if object.Name == "" {
@@ -491,10 +495,12 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
contents := make([]Object, 0, len(resp.Objects))
- var owner = Owner{}
+ var owner = Owner{
+ ID: globalMinioDefaultOwnerID,
+ DisplayName: "minio",
+ }
var data = ListObjectsResponse{}
- owner.ID = globalMinioDefaultOwnerID
for _, object := range resp.Objects {
var content = Object{}
if object.Name == "" {
@@ -538,12 +544,11 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
// generates an ListObjectsV2 response for the said bucket with other enumerated options.
func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata bool) ListObjectsV2Response {
contents := make([]Object, 0, len(objects))
- var owner = Owner{}
- var data = ListObjectsV2Response{}
-
- if fetchOwner {
- owner.ID = globalMinioDefaultOwnerID
+ var owner = Owner{
+ ID: globalMinioDefaultOwnerID,
+ DisplayName: "minio",
}
+ var data = ListObjectsV2Response{}
for _, object := range objects {
var content = Object{}
diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go
index 49f57f2f5..46fe06152 100644
--- a/cmd/bucket-handlers.go
+++ b/cmd/bucket-handlers.go
@@ -855,6 +855,12 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
}
object := formValues.Get("Key")
+ object, err = unescapePath(object)
+ if err != nil {
+ writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
+ return
+ }
+
successRedirect := formValues.Get("success_action_redirect")
successStatus := formValues.Get("success_action_status")
var redirectURL *url.URL
diff --git a/cmd/config/cache/help.go b/cmd/config/cache/help.go
index 100b157c6..bc196ef4d 100644
--- a/cmd/config/cache/help.go
+++ b/cmd/config/cache/help.go
@@ -40,19 +40,13 @@ var (
},
config.HelpKV{
Key: Exclude,
- Description: `comma separated wildcard exclusion patterns e.g. "bucket/*.tmp,*.exe"`,
+ Description: `exclude cache for following patterns e.g. "bucket/*.tmp,*.exe"`,
Optional: true,
Type: "csv",
},
- config.HelpKV{
- Key: config.Comment,
- Description: config.DefaultComment,
- Optional: true,
- Type: "sentence",
- },
config.HelpKV{
Key: After,
- Description: `minimum accesses before caching an object`,
+ Description: `minimum number of access before caching an object`,
Optional: true,
Type: "number",
},
@@ -80,5 +74,11 @@ var (
Optional: true,
Type: "string",
},
+ config.HelpKV{
+ Key: config.Comment,
+ Description: config.DefaultComment,
+ Optional: true,
+ Type: "sentence",
+ },
}
)
diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go
index 332a56894..fabaeb4cf 100644
--- a/cmd/object-handlers.go
+++ b/cmd/object-handlers.go
@@ -115,7 +115,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
}
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -322,7 +322,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
}
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -561,7 +561,7 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
}
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -876,7 +876,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
}
vars := mux.Vars(r)
dstBucket := vars["bucket"]
- dstObject, err := url.PathUnescape(vars["object"])
+ dstObject, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -1395,7 +1395,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -1703,7 +1703,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -1831,7 +1831,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
vars := mux.Vars(r)
dstBucket := vars["bucket"]
- dstObject, err := url.PathUnescape(vars["object"])
+ dstObject, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -2153,7 +2153,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
}
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -2391,7 +2391,7 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter,
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -2431,7 +2431,7 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -2570,7 +2570,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -2765,7 +2765,7 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -2941,7 +2941,7 @@ func (api objectAPIHandlers) PutObjectLegalHoldHandler(w http.ResponseWriter, r
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -3040,7 +3040,7 @@ func (api objectAPIHandlers) GetObjectLegalHoldHandler(w http.ResponseWriter, r
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -3105,7 +3105,7 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -3211,7 +3211,7 @@ func (api objectAPIHandlers) GetObjectRetentionHandler(w http.ResponseWriter, r
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -3271,7 +3271,7 @@ func (api objectAPIHandlers) GetObjectTaggingHandler(w http.ResponseWriter, r *h
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -3321,7 +3321,7 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -3409,7 +3409,7 @@ func (api objectAPIHandlers) DeleteObjectTaggingHandler(w http.ResponseWriter, r
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
@@ -3471,7 +3471,7 @@ func (api objectAPIHandlers) PostRestoreObjectHandler(w http.ResponseWriter, r *
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
diff --git a/cmd/object-handlers_test.go b/cmd/object-handlers_test.go
index 396ee398f..4178529a2 100644
--- a/cmd/object-handlers_test.go
+++ b/cmd/object-handlers_test.go
@@ -2983,7 +2983,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
}
if actualError.Key != objectName {
- t.Errorf("MinIO %s: error response object name differs from expected value", instanceType)
+ t.Errorf("MinIO %s: error response object name (%s) differs from expected value (%s)", instanceType, actualError.Key, objectName)
}
}
diff --git a/cmd/server_test.go b/cmd/server_test.go
index adcecba4b..ec0d38e0a 100644
--- a/cmd/server_test.go
+++ b/cmd/server_test.go
@@ -1267,20 +1267,7 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *check) {
c.Assert(response.StatusCode, http.StatusBadRequest)
verifyError(c, response, "KeyTooLongError", "Your key is too long", http.StatusBadRequest)
- // make object name with prefix as slash
- longObjName = fmt.Sprintf("/%0255d/%0255d", 1, 1)
- buffer = bytes.NewReader([]byte("hello world"))
- // create new HTTP request to insert the object.
- request, err = newTestSignedRequest(http.MethodPut, getPutObjectURL(s.endPoint, bucketName, longObjName),
- int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer)
- c.Assert(err, nil)
- // execute the HTTP request.
- response, err = s.client.Do(request)
- c.Assert(err, nil)
- c.Assert(response.StatusCode, http.StatusBadRequest)
- verifyError(c, response, "XMinioInvalidObjectName", "Object name contains a leading slash.", http.StatusBadRequest)
-
- //make object name as unsupported
+ // make object name as unsupported
longObjName = fmt.Sprintf("%0256d", 1)
buffer = bytes.NewReader([]byte("hello world"))
request, err = newTestSignedRequest(http.MethodPut, getPutObjectURL(s.endPoint, bucketName, longObjName),
@@ -1595,14 +1582,14 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *check) {
[]string{
"foo bar 1",
"foo bar 2",
- "",
+ fmt.Sprintf("%sminio", globalMinioDefaultOwnerID),
},
},
{getListObjectsV2URL(s.endPoint, bucketName, "", "1000", "true", ""),
[]string{
"foo bar 1",
"foo bar 2",
- fmt.Sprintf("%s", globalMinioDefaultOwnerID),
+ fmt.Sprintf("%sminio", globalMinioDefaultOwnerID),
},
},
{getListObjectsV2URL(s.endPoint, bucketName, "", "1000", "", "url"), []string{"foo+bar+1", "foo+bar+2"}},
diff --git a/cmd/utils.go b/cmd/utils.go
index 1a5ca5ef2..5834630a8 100644
--- a/cmd/utils.go
+++ b/cmd/utils.go
@@ -29,6 +29,7 @@ import (
"net/http"
"net/url"
"os"
+ "path"
"path/filepath"
"reflect"
"runtime"
@@ -686,18 +687,51 @@ func ceilFrac(numerator, denominator int64) (ceil int64) {
return
}
+// unescapeGeneric is similar to url.PathUnescape or url.QueryUnescape
+// depending on input, additionally also handles situations such as
+// `//` are normalized as `/`, also removes any `/` prefix before
+// returning.
+func unescapeGeneric(p string, escapeFn func(string) (string, error)) (string, error) {
+ ep, err := escapeFn(p)
+ if err != nil {
+ return "", err
+ }
+ if len(ep) > 0 && ep[0] == '/' {
+ // Path ends with '/' preserve it
+ if ep[len(ep)-1] == '/' {
+ ep = path.Clean(ep)
+ ep += slashSeparator
+ } else {
+ ep = path.Clean(ep)
+ }
+ ep = ep[1:]
+ }
+ return ep, nil
+}
+
+// unescapePath is similar to unescapeGeneric but for specifically
+// path unescaping.
+func unescapePath(p string) (string, error) {
+ return unescapeGeneric(p, url.PathUnescape)
+}
+
+// similar to unescapeGeneric but never returns any error if the unescaping
+// fails, returns the input as is in such occasion, not meant to be
+// used where strict validation is expected.
+func likelyUnescapeGeneric(p string, escapeFn func(string) (string, error)) string {
+ ep, err := unescapeGeneric(p, escapeFn)
+ if err != nil {
+ return p
+ }
+ return ep
+}
+
// Returns context with ReqInfo details set in the context.
func newContext(r *http.Request, w http.ResponseWriter, api string) context.Context {
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
- if err != nil {
- object = vars["object"]
- }
- prefix, err := url.QueryUnescape(vars["prefix"])
- if err != nil {
- prefix = vars["prefix"]
- }
+ object := likelyUnescapeGeneric(vars["object"], url.PathUnescape)
+ prefix := likelyUnescapeGeneric(vars["prefix"], url.QueryUnescape)
if prefix != "" {
object = prefix
}
diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go
index 17fed2bdd..d7e751f29 100644
--- a/cmd/web-handlers.go
+++ b/cmd/web-handlers.go
@@ -1107,7 +1107,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeWebErrorResponse(w, err)
return
@@ -1353,7 +1353,7 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
- object, err := url.PathUnescape(vars["object"])
+ object, err := unescapePath(vars["object"])
if err != nil {
writeWebErrorResponse(w, err)
return