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

View file

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

View file

@ -21,6 +21,8 @@ import (
"io"
"io/ioutil"
"net/http"
"runtime"
"strings"
humanize "github.com/dustin/go-humanize"
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.
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)
}

View file

@ -111,12 +111,12 @@ func isValidResources(resources set.StringSet) (err error) {
return err
}
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")
return err
}
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")
return err
}
@ -282,7 +282,7 @@ func checkBucketPolicyResources(bucket string, bucketPolicy *bucketPolicy) APIEr
// nesting. Reject such rules.
for _, otherResource := range resources {
// Common prefix reject such rules.
if strings.HasPrefix(otherResource, resource) {
if hasPrefix(otherResource, resource) {
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) {
if r.Method == httpGET && guessIsBrowserReq(r) && globalIsBrowserEnabled {
// For all browser requests set appropriate Cache-Control policies
if strings.HasPrefix(r.URL.Path, reservedBucket+"/") {
if strings.HasSuffix(r.URL.Path, ".js") || r.URL.Path == reservedBucket+"/favicon.ico" {
if hasPrefix(r.URL.Path, reservedBucket+"/") {
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
// of the asset name will change and hence it can not be served from cache.
w.Header().Set("Cache-Control", "max-age=31536000")

View file

@ -16,10 +16,7 @@
package cmd
import (
"strings"
"time"
)
import "time"
// 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.
@ -82,7 +79,7 @@ func listLocksInfo(bucket, prefix string, duration time.Duration) []VolumeLockIn
continue
}
// N B empty prefix matches all param.path.
if !strings.HasPrefix(param.path, prefix) {
if !hasPrefix(param.path, prefix) {
continue
}

View file

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

View file

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

View file

@ -22,6 +22,7 @@ import (
"io"
"path"
"regexp"
"runtime"
"strings"
"unicode/utf8"
@ -91,10 +92,10 @@ func IsValidObjectName(object string) bool {
if len(object) == 0 {
return false
}
if strings.HasSuffix(object, slashSeparator) {
if hasSuffix(object, slashSeparator) {
return false
}
if strings.HasPrefix(object, slashSeparator) {
if hasPrefix(object, slashSeparator) {
return false
}
return IsValidObjectPrefix(object)
@ -159,6 +160,26 @@ func getCompleteMultipartMD5(parts []completePart) (string, error) {
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.
type byBucketName []BucketInfo

View file

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

View file

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

View file

@ -69,26 +69,6 @@ func setupTestReadDirEmpty(t *testing.T) (testResults []result) {
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.
func setupTestReadDirFiles(t *testing.T) (testResults []result) {
dir := mustSetupDir(t)
@ -198,8 +178,6 @@ func TestReadDir(t *testing.T) {
// Setup and capture test results for empty directory.
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.
testResults = append(testResults, setupTestReadDirFiles(t)...)
// 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 {
break
}
if strings.HasPrefix(entries[start], prefixEntry) {
if hasPrefix(entries[start], prefixEntry) {
break
}
start++
@ -76,7 +76,7 @@ func filterMatchingPrefix(entries []string, prefixEntry string) []string {
if start == end {
break
}
if strings.HasPrefix(entries[end-1], prefixEntry) {
if hasPrefix(entries[end-1], prefixEntry) {
break
}
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.
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
// 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.
@ -182,7 +182,7 @@ func doTreeWalk(bucket, prefixDir, entryPrefixMatch, marker string, recursive bo
continue
}
}
if recursive && strings.HasSuffix(entry, slashSeparator) {
if recursive && hasSuffix(entry, slashSeparator) {
// If the entry is a directory, we will need recurse into it.
markerArg := ""
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.
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)
}
}