all/windows: Be case in-sensitive about pattern matching. (#3682)

Resource strings and paths are case insensitive on windows
deployments but if user happens to use upper case instead of
lower case for certain configuration params like bucket
policies and bucket notification config. We might not honor
them which leads to a wrong behavior on windows.

This is windows only behavior, for all other platforms case
is still kept sensitive.
This commit is contained in:
Harshavardhana 2017-02-03 23:27:50 -08:00 committed by GitHub
parent b6ebf2aba8
commit 533338bdeb
15 changed files with 48 additions and 97 deletions

View File

@ -18,7 +18,6 @@ package cmd
import ( import (
"net/http" "net/http"
"strings"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
@ -44,7 +43,7 @@ func validateListObjectsArgs(prefix, marker, delimiter string, maxKeys int) APIE
// Marker is set validate pre-condition. // Marker is set validate pre-condition.
if marker != "" { if marker != "" {
// Marker not common with prefix is not implemented. // Marker not common with prefix is not implemented.
if !strings.HasPrefix(marker, prefix) { if !hasPrefix(marker, prefix) {
return ErrNotImplemented return ErrNotImplemented
} }
} }

View File

@ -165,7 +165,7 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter,
} }
if keyMarker != "" { if keyMarker != "" {
// Marker not common with prefix is not implemented. // Marker not common with prefix is not implemented.
if !strings.HasPrefix(keyMarker, prefix) { if !hasPrefix(keyMarker, prefix) {
writeErrorResponse(w, ErrNotImplemented, r.URL) writeErrorResponse(w, ErrNotImplemented, r.URL)
return return
} }

View File

@ -21,6 +21,8 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"runtime"
"strings"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
mux "github.com/gorilla/mux" mux "github.com/gorilla/mux"
@ -63,6 +65,10 @@ func bucketPolicyActionMatch(action string, statement policyStatement) bool {
// Match function matches wild cards in 'pattern' for resource. // Match function matches wild cards in 'pattern' for resource.
func resourceMatch(pattern, resource string) bool { func resourceMatch(pattern, resource string) bool {
if runtime.GOOS == "windows" {
// For windows specifically make sure we are case insensitive.
return wildcard.Match(strings.ToLower(pattern), strings.ToLower(resource))
}
return wildcard.Match(pattern, resource) return wildcard.Match(pattern, resource)
} }

View File

@ -111,12 +111,12 @@ func isValidResources(resources set.StringSet) (err error) {
return err return err
} }
for resource := range resources { for resource := range resources {
if !strings.HasPrefix(resource, bucketARNPrefix) { if !hasPrefix(resource, bucketARNPrefix) {
err = errors.New("Unsupported resource style found: " + resource + ", please validate your policy document") err = errors.New("Unsupported resource style found: " + resource + ", please validate your policy document")
return err return err
} }
resourceSuffix := strings.SplitAfter(resource, bucketARNPrefix)[1] resourceSuffix := strings.SplitAfter(resource, bucketARNPrefix)[1]
if len(resourceSuffix) == 0 || strings.HasPrefix(resourceSuffix, "/") { if len(resourceSuffix) == 0 || hasPrefix(resourceSuffix, "/") {
err = errors.New("Invalid resource style found: " + resource + ", please validate your policy document") err = errors.New("Invalid resource style found: " + resource + ", please validate your policy document")
return err return err
} }
@ -282,7 +282,7 @@ func checkBucketPolicyResources(bucket string, bucketPolicy *bucketPolicy) APIEr
// nesting. Reject such rules. // nesting. Reject such rules.
for _, otherResource := range resources { for _, otherResource := range resources {
// Common prefix reject such rules. // Common prefix reject such rules.
if strings.HasPrefix(otherResource, resource) { if hasPrefix(otherResource, resource) {
return ErrPolicyNesting return ErrPolicyNesting
} }
} }

View File

@ -141,8 +141,8 @@ func setBrowserCacheControlHandler(h http.Handler) http.Handler {
func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == httpGET && guessIsBrowserReq(r) && globalIsBrowserEnabled { if r.Method == httpGET && guessIsBrowserReq(r) && globalIsBrowserEnabled {
// For all browser requests set appropriate Cache-Control policies // For all browser requests set appropriate Cache-Control policies
if strings.HasPrefix(r.URL.Path, reservedBucket+"/") { if hasPrefix(r.URL.Path, reservedBucket+"/") {
if strings.HasSuffix(r.URL.Path, ".js") || r.URL.Path == reservedBucket+"/favicon.ico" { if hasSuffix(r.URL.Path, ".js") || r.URL.Path == reservedBucket+"/favicon.ico" {
// For assets set cache expiry of one year. For each release, the name // For assets set cache expiry of one year. For each release, the name
// of the asset name will change and hence it can not be served from cache. // of the asset name will change and hence it can not be served from cache.
w.Header().Set("Cache-Control", "max-age=31536000") w.Header().Set("Cache-Control", "max-age=31536000")

View File

@ -16,10 +16,7 @@
package cmd package cmd
import ( import "time"
"strings"
"time"
)
// SystemLockState - Structure to fill the lock state of entire object storage. // SystemLockState - Structure to fill the lock state of entire object storage.
// That is the total locks held, total calls blocked on locks and state of all the locks for the entire system. // That is the total locks held, total calls blocked on locks and state of all the locks for the entire system.
@ -82,7 +79,7 @@ func listLocksInfo(bucket, prefix string, duration time.Duration) []VolumeLockIn
continue continue
} }
// N B empty prefix matches all param.path. // N B empty prefix matches all param.path.
if !strings.HasPrefix(param.path, prefix) { if !hasPrefix(param.path, prefix) {
continue continue
} }

View File

@ -18,7 +18,6 @@ package cmd
import ( import (
"errors" "errors"
"strings"
"github.com/minio/minio/pkg/wildcard" "github.com/minio/minio/pkg/wildcard"
) )
@ -206,9 +205,9 @@ func filterRuleMatch(object string, frs []filterRule) bool {
var prefixMatch, suffixMatch = true, true var prefixMatch, suffixMatch = true, true
for _, fr := range frs { for _, fr := range frs {
if isValidFilterNamePrefix(fr.Name) { if isValidFilterNamePrefix(fr.Name) {
prefixMatch = strings.HasPrefix(object, fr.Value) prefixMatch = hasPrefix(object, fr.Value)
} else if isValidFilterNameSuffix(fr.Name) { } else if isValidFilterNameSuffix(fr.Name) {
suffixMatch = strings.HasSuffix(object, fr.Value) suffixMatch = hasSuffix(object, fr.Value)
} }
} }
return prefixMatch && suffixMatch return prefixMatch && suffixMatch

View File

@ -16,11 +16,7 @@
package cmd package cmd
import ( import "github.com/skyrings/skyring-common/tools/uuid"
"strings"
"github.com/skyrings/skyring-common/tools/uuid"
)
// Checks on GetObject arguments, bucket and object. // Checks on GetObject arguments, bucket and object.
func checkGetObjArgs(bucket, object string) error { func checkGetObjArgs(bucket, object string) error {
@ -69,7 +65,7 @@ func checkListObjsArgs(bucket, prefix, marker, delimiter string, obj ObjectLayer
}) })
} }
// Verify if marker has prefix. // Verify if marker has prefix.
if marker != "" && !strings.HasPrefix(marker, prefix) { if marker != "" && !hasPrefix(marker, prefix) {
return traceError(InvalidMarkerPrefixCombination{ return traceError(InvalidMarkerPrefixCombination{
Marker: marker, Marker: marker,
Prefix: prefix, Prefix: prefix,
@ -84,7 +80,7 @@ func checkListMultipartArgs(bucket, prefix, keyMarker, uploadIDMarker, delimiter
return err return err
} }
if uploadIDMarker != "" { if uploadIDMarker != "" {
if strings.HasSuffix(keyMarker, slashSeparator) { if hasSuffix(keyMarker, slashSeparator) {
return traceError(InvalidUploadIDKeyCombination{ return traceError(InvalidUploadIDKeyCombination{
UploadIDMarker: uploadIDMarker, UploadIDMarker: uploadIDMarker,
KeyMarker: keyMarker, KeyMarker: keyMarker,

View File

@ -22,6 +22,7 @@ import (
"io" "io"
"path" "path"
"regexp" "regexp"
"runtime"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
@ -91,10 +92,10 @@ func IsValidObjectName(object string) bool {
if len(object) == 0 { if len(object) == 0 {
return false return false
} }
if strings.HasSuffix(object, slashSeparator) { if hasSuffix(object, slashSeparator) {
return false return false
} }
if strings.HasPrefix(object, slashSeparator) { if hasPrefix(object, slashSeparator) {
return false return false
} }
return IsValidObjectPrefix(object) return IsValidObjectPrefix(object)
@ -159,6 +160,26 @@ func getCompleteMultipartMD5(parts []completePart) (string, error) {
return s3MD5, nil return s3MD5, nil
} }
// Prefix matcher string matches prefix in a platform specific way.
// For example on windows since its case insensitive we are supposed
// to do case insensitive checks.
func hasPrefix(s string, prefix string) bool {
if runtime.GOOS == "windows" {
return strings.HasPrefix(strings.ToLower(s), strings.ToLower(prefix))
}
return strings.HasPrefix(s, prefix)
}
// Suffix matcher string matches suffix in a platform specific way.
// For example on windows since its case insensitive we are supposed
// to do case insensitive checks.
func hasSuffix(s string, suffix string) bool {
if runtime.GOOS == "windows" {
return strings.HasSuffix(strings.ToLower(s), strings.ToLower(suffix))
}
return strings.HasSuffix(s, suffix)
}
// byBucketName is a collection satisfying sort.Interface. // byBucketName is a collection satisfying sort.Interface.
type byBucketName []BucketInfo type byBucketName []BucketInfo

View File

@ -68,10 +68,6 @@ func parseDirents(dirPath string, buf []byte) (entries []string, err error) {
if name == "." || name == ".." { if name == "." || name == ".." {
continue continue
} }
// Skip special files.
if hasPosixReservedPrefix(name) {
continue
}
switch dirent.Type { switch dirent.Type {
case syscall.DT_DIR: case syscall.DT_DIR:

View File

@ -52,10 +52,6 @@ func readDir(dirPath string) (entries []string, err error) {
return nil, err return nil, err
} }
for _, fi := range fis { for _, fi := range fis {
// Skip special files, if found.
if hasPosixReservedPrefix(fi.Name()) {
continue
}
// Stat symbolic link and follow to get the final value. // Stat symbolic link and follow to get the final value.
if fi.Mode()&os.ModeSymlink == os.ModeSymlink { if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
var st os.FileInfo var st os.FileInfo

View File

@ -69,26 +69,6 @@ func setupTestReadDirEmpty(t *testing.T) (testResults []result) {
return testResults return testResults
} }
// Test to read empty directory with only reserved names.
func setupTestReadDirReserved(t *testing.T) (testResults []result) {
dir := mustSetupDir(t)
entries := []string{}
// Create a file with reserved name.
for _, reservedName := range posixReservedPrefix {
if err := ioutil.WriteFile(filepath.Join(dir, reservedName), []byte{}, os.ModePerm); err != nil {
// For cleanup, its required to add these entries into test results.
testResults = append(testResults, result{dir, entries})
t.Fatalf("Unable to create file, %s", err)
}
// entries = append(entries, reservedName) - reserved files are skipped.
}
sort.Strings(entries)
// Add entries slice for this test directory.
testResults = append(testResults, result{dir, entries})
return testResults
}
// Test to read non-empty directory with only files. // Test to read non-empty directory with only files.
func setupTestReadDirFiles(t *testing.T) (testResults []result) { func setupTestReadDirFiles(t *testing.T) (testResults []result) {
dir := mustSetupDir(t) dir := mustSetupDir(t)
@ -198,8 +178,6 @@ func TestReadDir(t *testing.T) {
// Setup and capture test results for empty directory. // Setup and capture test results for empty directory.
testResults = append(testResults, setupTestReadDirEmpty(t)...) testResults = append(testResults, setupTestReadDirEmpty(t)...)
// Setup and capture test results for reserved files.
testResults = append(testResults, setupTestReadDirReserved(t)...)
// Setup and capture test results for directory with only files. // Setup and capture test results for directory with only files.
testResults = append(testResults, setupTestReadDirFiles(t)...) testResults = append(testResults, setupTestReadDirFiles(t)...)
// Setup and capture test results for directory with files and directories. // Setup and capture test results for directory with files and directories.

View File

@ -1,37 +0,0 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import "strings"
// List of reserved words for files, includes old and new ones.
var posixReservedPrefix = []string{
"$tmpfile",
// Add new reserved words if any used in future.
}
// hasPosixReservedPrefix - has reserved prefix.
func hasPosixReservedPrefix(name string) (isReserved bool) {
for _, reservedKey := range posixReservedPrefix {
if strings.HasPrefix(name, reservedKey) {
isReserved = true
break
}
}
return isReserved
}

View File

@ -67,7 +67,7 @@ func filterMatchingPrefix(entries []string, prefixEntry string) []string {
if start == end { if start == end {
break break
} }
if strings.HasPrefix(entries[start], prefixEntry) { if hasPrefix(entries[start], prefixEntry) {
break break
} }
start++ start++
@ -76,7 +76,7 @@ func filterMatchingPrefix(entries []string, prefixEntry string) []string {
if start == end { if start == end {
break break
} }
if strings.HasPrefix(entries[end-1], prefixEntry) { if hasPrefix(entries[end-1], prefixEntry) {
break break
} }
end-- end--
@ -173,7 +173,7 @@ func doTreeWalk(bucket, prefixDir, entryPrefixMatch, marker string, recursive bo
// Skip as the marker would already be listed in the previous listing. // Skip as the marker would already be listed in the previous listing.
continue continue
} }
if recursive && !strings.HasSuffix(entry, slashSeparator) { if recursive && !hasSuffix(entry, slashSeparator) {
// We should not skip for recursive listing and if markerDir is a directory // We should not skip for recursive listing and if markerDir is a directory
// for ex. if marker is "four/five.txt" markerDir will be "four/" which // for ex. if marker is "four/five.txt" markerDir will be "four/" which
// should not be skipped, instead it will need to be treeWalk()'ed into. // should not be skipped, instead it will need to be treeWalk()'ed into.
@ -182,7 +182,7 @@ func doTreeWalk(bucket, prefixDir, entryPrefixMatch, marker string, recursive bo
continue continue
} }
} }
if recursive && strings.HasSuffix(entry, slashSeparator) { if recursive && hasSuffix(entry, slashSeparator) {
// If the entry is a directory, we will need recurse into it. // If the entry is a directory, we will need recurse into it.
markerArg := "" markerArg := ""
if entry == markerDir { if entry == markerDir {

View File

@ -135,7 +135,7 @@ func testTreeWalkPrefix(t *testing.T, listDir listDirFunc, isLeaf isLeafFunc) {
// Check if all entries received on the channel match the prefix. // Check if all entries received on the channel match the prefix.
for res := range twResultCh { for res := range twResultCh {
if !strings.HasPrefix(res.entry, prefix) { if !hasPrefix(res.entry, prefix) {
t.Errorf("Entry %s doesn't match prefix %s", res.entry, prefix) t.Errorf("Entry %s doesn't match prefix %s", res.entry, prefix)
} }
} }