| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  |  * Minio Cloud Storage, (C) 2015-2016 Minio, Inc. | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * 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 fs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2016-01-27 13:55:50 +08:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 	"path/filepath" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-11 08:40:09 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/probe" | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | // ListObjects - lists all objects for a given prefix, returns up to
 | 
					
						
							|  |  |  | // maxKeys number of objects per call.
 | 
					
						
							|  |  |  | func (fs Filesystem) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) { | 
					
						
							|  |  |  | 	result := ListObjectsResult{} | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 	var queryPrefix string | 
					
						
							| 
									
										
										
										
											2016-01-26 18:19:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 	// Input validation.
 | 
					
						
							|  |  |  | 	if !IsValidBucketName(bucket) { | 
					
						
							|  |  |  | 		return result, probe.NewError(BucketNameInvalid{Bucket: bucket}) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-01-26 18:19:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 	bucket = fs.denormalizeBucket(bucket) | 
					
						
							| 
									
										
										
										
											2016-01-26 18:19:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-12 11:47:44 +08:00
										 |  |  | 	if status, err := isDirExist(filepath.Join(fs.path, bucket)); !status { | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 		if err == nil { | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 			// File exists, but its not a directory.
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 			return result, probe.NewError(BucketNotFound{Bucket: bucket}) | 
					
						
							|  |  |  | 		} else if os.IsNotExist(err) { | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 			// File does not exist.
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 			return result, probe.NewError(BucketNotFound{Bucket: bucket}) | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 			return result, probe.NewError(err) | 
					
						
							| 
									
										
										
										
											2016-01-20 09:49:48 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-01-26 18:19:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 	if delimiter != "" && delimiter != "/" { | 
					
						
							|  |  |  | 		return result, probe.NewError(fmt.Errorf("delimiter '%s' is not supported", delimiter)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if marker != "" { | 
					
						
							|  |  |  | 		if markerUnescaped, err := url.QueryUnescape(marker); err == nil { | 
					
						
							|  |  |  | 			marker = markerUnescaped | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			return result, probe.NewError(err) | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 		if !strings.HasPrefix(marker, prefix) { | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 			return result, probe.NewError(fmt.Errorf("Invalid combination of marker '%s' and prefix '%s'", marker, prefix)) | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-01-26 18:19:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-17 08:49:16 +08:00
										 |  |  | 	// Return empty response for a valid request when maxKeys is 0.
 | 
					
						
							|  |  |  | 	if maxKeys == 0 { | 
					
						
							|  |  |  | 		return result, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if maxKeys < 0 || maxKeys > listObjectsLimit { | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 		maxKeys = listObjectsLimit | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-01-26 18:19:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 	bucketDir := filepath.Join(fs.path, bucket) | 
					
						
							| 
									
										
										
										
											2016-01-26 18:19:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 	recursive := true | 
					
						
							|  |  |  | 	skipDir := true | 
					
						
							|  |  |  | 	if delimiter == "/" { | 
					
						
							|  |  |  | 		skipDir = false | 
					
						
							|  |  |  | 		recursive = false | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-01-26 18:19:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 	prefixDir := filepath.Dir(filepath.FromSlash(prefix)) | 
					
						
							|  |  |  | 	rootDir := filepath.Join(bucketDir, prefixDir) | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 	// Maximum 1000 objects returned in a single to listObjects.
 | 
					
						
							|  |  |  | 	// Further calls will set right marker value to continue reading the rest of the objectList.
 | 
					
						
							|  |  |  | 	// popListObjectCh returns nil if the call to ListObject is done for the first time.
 | 
					
						
							|  |  |  | 	// On further calls to ListObjects to retrive more objects within the timeout period,
 | 
					
						
							|  |  |  | 	// popListObjectCh returns the channel from which rest of the objects can be retrieved.
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 	objectInfoCh := fs.popListObjectCh(ListObjectParams{bucket, delimiter, marker, prefix}) | 
					
						
							|  |  |  | 	if objectInfoCh == nil { | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 		if prefix != "" { | 
					
						
							|  |  |  | 			// queryPrefix variable is set to value of the prefix to be searched.
 | 
					
						
							|  |  |  | 			// If prefix contains directory hierarchy queryPrefix is set to empty string,
 | 
					
						
							|  |  |  | 			// this ensure that all objects inside the prefixDir is listed.
 | 
					
						
							|  |  |  | 			// Otherwise the base name is extracted from path.Base and it'll be will be set to Querystring.
 | 
					
						
							|  |  |  | 			// if prefix = /Asia/India/, queryPrefix will be set to empty string(""), so that all objects in prefixDir are listed.
 | 
					
						
							|  |  |  | 			// if prefix = /Asia/India/summerpics , Querystring will be set to "summerpics",
 | 
					
						
							|  |  |  | 			// so those all objects with the prefix "summerpics" inside the /Asia/India/ prefix folder gets listed.
 | 
					
						
							|  |  |  | 			if prefix[len(prefix)-1:] == "/" { | 
					
						
							|  |  |  | 				queryPrefix = "" | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				queryPrefix = path.Base(prefix) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		ch := treeWalk(rootDir, bucketDir, recursive, queryPrefix) | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 		objectInfoCh = &ch | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 	nextMarker := "" | 
					
						
							|  |  |  | 	for i := 0; i < maxKeys; { | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 		objInfo, ok := objectInfoCh.Read() | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 			// Closed channel.
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 			return result, nil | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 		if objInfo.Err != nil { | 
					
						
							|  |  |  | 			return ListObjectsResult{}, probe.NewError(objInfo.Err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-01-27 13:55:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 		if strings.Contains(objInfo.Name, "$multiparts") || strings.Contains(objInfo.Name, "$tmpobject") { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 		if objInfo.IsDir && skipDir { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-12 08:31:24 +08:00
										 |  |  | 		// Add the bucket.
 | 
					
						
							|  |  |  | 		objInfo.Bucket = bucket | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 		// In case its not the first call to ListObjects (before timeout),
 | 
					
						
							|  |  |  | 		// The result is already inside the buffered channel.
 | 
					
						
							|  |  |  | 		if objInfo.Name > marker { | 
					
						
							|  |  |  | 			if objInfo.IsDir { | 
					
						
							|  |  |  | 				result.Prefixes = append(result.Prefixes, objInfo.Name) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				result.Objects = append(result.Objects, objInfo) | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 			nextMarker = objInfo.Name | 
					
						
							|  |  |  | 			i++ | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-03-17 08:30:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if !objectInfoCh.IsClosed() { | 
					
						
							|  |  |  | 		result.IsTruncated = true | 
					
						
							|  |  |  | 		result.NextMarker = nextMarker | 
					
						
							|  |  |  | 		fs.pushListObjectCh(ListObjectParams{bucket, delimiter, nextMarker, prefix}, *objectInfoCh) | 
					
						
							| 
									
										
										
										
											2015-11-10 19:10:11 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-02-18 16:38:58 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return result, nil | 
					
						
							| 
									
										
										
										
											2015-10-23 06:37:45 +08:00
										 |  |  | } |