github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/bucket-listobjects-handlers.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "net/http" 23 "strconv" 24 "strings" 25 26 "github.com/minio/minio/internal/logger" 27 "github.com/minio/mux" 28 29 "github.com/minio/pkg/v2/policy" 30 ) 31 32 // Validate all the ListObjects query arguments, returns an APIErrorCode 33 // if one of the args do not meet the required conditions. 34 // Special conditions required by MinIO server are as below 35 // - delimiter if set should be equal to '/', otherwise the request is rejected. 36 // - marker if set should have a common prefix with 'prefix' param, otherwise 37 // the request is rejected. 38 func validateListObjectsArgs(prefix, marker, delimiter, encodingType string, maxKeys int) APIErrorCode { 39 // Max keys cannot be negative. 40 if maxKeys < 0 { 41 return ErrInvalidMaxKeys 42 } 43 44 if encodingType != "" { 45 // AWS S3 spec only supports 'url' encoding type 46 if !strings.EqualFold(encodingType, "url") { 47 return ErrInvalidEncodingMethod 48 } 49 } 50 51 if !IsValidObjectPrefix(prefix) { 52 return ErrInvalidObjectName 53 } 54 55 if marker != "" && !HasPrefix(marker, prefix) { 56 return ErrNotImplemented 57 } 58 59 return ErrNone 60 } 61 62 func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r *http.Request) { 63 api.listObjectVersionsHandler(w, r, false) 64 } 65 66 func (api objectAPIHandlers) ListObjectVersionsMHandler(w http.ResponseWriter, r *http.Request) { 67 api.listObjectVersionsHandler(w, r, true) 68 } 69 70 // ListObjectVersionsHandler - GET Bucket Object versions 71 // You can use the versions subresource to list metadata about all 72 // of the versions of objects in a bucket. 73 func (api objectAPIHandlers) listObjectVersionsHandler(w http.ResponseWriter, r *http.Request, metadata bool) { 74 ctx := newContext(r, w, "ListObjectVersions") 75 76 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 77 78 vars := mux.Vars(r) 79 bucket := vars["bucket"] 80 81 objectAPI := api.ObjectAPI() 82 if objectAPI == nil { 83 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) 84 return 85 } 86 87 if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketVersionsAction, bucket, ""); s3Error != ErrNone { 88 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) 89 return 90 } 91 var checkObjMeta metaCheckFn 92 if metadata { 93 checkObjMeta = func(name string, action policy.Action) (s3Err APIErrorCode) { 94 return checkRequestAuthType(ctx, r, action, bucket, name) 95 } 96 } 97 urlValues := r.Form 98 99 // Extract all the listBucketVersions query params to their native values. 100 prefix, marker, delimiter, maxkeys, encodingType, versionIDMarker, errCode := getListBucketObjectVersionsArgs(urlValues) 101 if errCode != ErrNone { 102 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(errCode), r.URL) 103 return 104 } 105 106 // Validate the query params before beginning to serve the request. 107 if s3Error := validateListObjectsArgs(prefix, marker, delimiter, encodingType, maxkeys); s3Error != ErrNone { 108 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) 109 return 110 } 111 112 listObjectVersions := objectAPI.ListObjectVersions 113 114 // Initiate a list object versions operation based on the input params. 115 // On success would return back ListObjectsInfo object to be 116 // marshaled into S3 compatible XML header. 117 listObjectVersionsInfo, err := listObjectVersions(ctx, bucket, prefix, marker, versionIDMarker, delimiter, maxkeys) 118 if err != nil { 119 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 120 return 121 } 122 123 if err = DecryptETags(ctx, GlobalKMS, listObjectVersionsInfo.Objects); err != nil { 124 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 125 return 126 } 127 response := generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo, checkObjMeta) 128 129 // Write success response. 130 writeSuccessResponseXML(w, encodeResponseList(response)) 131 } 132 133 // ListObjectsV2MHandler - GET Bucket (List Objects) Version 2 with metadata. 134 // -------------------------- 135 // This implementation of the GET operation returns some or all (up to 1000) 136 // of the objects in a bucket. You can use the request parameters as selection 137 // criteria to return a subset of the objects in a bucket. 138 // 139 // NOTE: It is recommended that this API to be used for application development. 140 // MinIO continues to support ListObjectsV1 and V2 for supporting legacy tools. 141 func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *http.Request) { 142 ctx := newContext(r, w, "ListObjectsV2M") 143 api.listObjectsV2Handler(ctx, w, r, true) 144 } 145 146 // ListObjectsV2Handler - GET Bucket (List Objects) Version 2. 147 // -------------------------- 148 // This implementation of the GET operation returns some or all (up to 1000) 149 // of the objects in a bucket. You can use the request parameters as selection 150 // criteria to return a subset of the objects in a bucket. 151 // 152 // NOTE: It is recommended that this API to be used for application development. 153 // MinIO continues to support ListObjectsV1 for supporting legacy tools. 154 func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) { 155 ctx := newContext(r, w, "ListObjectsV2") 156 api.listObjectsV2Handler(ctx, w, r, false) 157 } 158 159 // listObjectsV2Handler performs listing either with or without extra metadata. 160 func (api objectAPIHandlers) listObjectsV2Handler(ctx context.Context, w http.ResponseWriter, r *http.Request, metadata bool) { 161 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 162 163 vars := mux.Vars(r) 164 bucket := vars["bucket"] 165 166 objectAPI := api.ObjectAPI() 167 if objectAPI == nil { 168 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) 169 return 170 } 171 172 if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone { 173 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) 174 return 175 } 176 177 var checkObjMeta metaCheckFn 178 if metadata { 179 checkObjMeta = func(name string, action policy.Action) (s3Err APIErrorCode) { 180 return checkRequestAuthType(ctx, r, action, bucket, name) 181 } 182 } 183 urlValues := r.Form 184 185 // Extract all the listObjectsV2 query params to their native values. 186 prefix, token, startAfter, delimiter, fetchOwner, maxKeys, encodingType, errCode := getListObjectsV2Args(urlValues) 187 if errCode != ErrNone { 188 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(errCode), r.URL) 189 return 190 } 191 192 // Validate the query params before beginning to serve the request. 193 if s3Error := validateListObjectsArgs(prefix, token, delimiter, encodingType, maxKeys); s3Error != ErrNone { 194 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) 195 return 196 } 197 198 var ( 199 listObjectsV2Info ListObjectsV2Info 200 err error 201 ) 202 203 if r.Header.Get(xMinIOExtract) == "true" && strings.Contains(prefix, archivePattern) { 204 // Initiate a list objects operation inside a zip file based in the input params 205 listObjectsV2Info, err = listObjectsV2InArchive(ctx, objectAPI, bucket, prefix, token, delimiter, maxKeys, fetchOwner, startAfter) 206 } else { 207 // Initiate a list objects operation based on the input params. 208 // On success would return back ListObjectsInfo object to be 209 // marshaled into S3 compatible XML header. 210 listObjectsV2Info, err = objectAPI.ListObjectsV2(ctx, bucket, prefix, token, delimiter, maxKeys, fetchOwner, startAfter) 211 } 212 if err != nil { 213 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 214 return 215 } 216 217 if err = DecryptETags(ctx, GlobalKMS, listObjectsV2Info.Objects); err != nil { 218 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 219 return 220 } 221 222 response := generateListObjectsV2Response(bucket, prefix, token, listObjectsV2Info.NextContinuationToken, startAfter, 223 delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated, 224 maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, checkObjMeta) 225 226 // Write success response. 227 writeSuccessResponseXML(w, encodeResponseList(response)) 228 } 229 230 func parseRequestToken(token string) (subToken string, nodeIndex int) { 231 if token == "" { 232 return token, -1 233 } 234 i := strings.Index(token, ":") 235 if i < 0 { 236 return token, -1 237 } 238 nodeIndex, err := strconv.Atoi(token[i+1:]) 239 if err != nil { 240 return token, -1 241 } 242 subToken = token[:i] 243 return subToken, nodeIndex 244 } 245 246 func proxyRequestByToken(ctx context.Context, w http.ResponseWriter, r *http.Request, token string) (string, bool) { 247 subToken, nodeIndex := parseRequestToken(token) 248 if nodeIndex > 0 { 249 return subToken, proxyRequestByNodeIndex(ctx, w, r, nodeIndex) 250 } 251 return subToken, false 252 } 253 254 func proxyRequestByNodeIndex(ctx context.Context, w http.ResponseWriter, r *http.Request, index int) (success bool) { 255 if len(globalProxyEndpoints) == 0 { 256 return false 257 } 258 if index < 0 || index >= len(globalProxyEndpoints) { 259 return false 260 } 261 ep := globalProxyEndpoints[index] 262 if ep.IsLocal { 263 return false 264 } 265 return proxyRequest(ctx, w, r, ep) 266 } 267 268 // ListObjectsV1Handler - GET Bucket (List Objects) Version 1. 269 // -------------------------- 270 // This implementation of the GET operation returns some or all (up to 1000) 271 // of the objects in a bucket. You can use the request parameters as selection 272 // criteria to return a subset of the objects in a bucket. 273 func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) { 274 ctx := newContext(r, w, "ListObjectsV1") 275 276 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 277 278 vars := mux.Vars(r) 279 bucket := vars["bucket"] 280 281 objectAPI := api.ObjectAPI() 282 if objectAPI == nil { 283 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) 284 return 285 } 286 287 if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone { 288 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) 289 return 290 } 291 292 // Extract all the listObjectsV1 query params to their native values. 293 prefix, marker, delimiter, maxKeys, encodingType, s3Error := getListObjectsV1Args(r.Form) 294 if s3Error != ErrNone { 295 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) 296 return 297 } 298 299 // Validate all the query params before beginning to serve the request. 300 if s3Error := validateListObjectsArgs(prefix, marker, delimiter, encodingType, maxKeys); s3Error != ErrNone { 301 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) 302 return 303 } 304 305 listObjects := objectAPI.ListObjects 306 307 // Initiate a list objects operation based on the input params. 308 // On success would return back ListObjectsInfo object to be 309 // marshaled into S3 compatible XML header. 310 listObjectsInfo, err := listObjects(ctx, bucket, prefix, marker, delimiter, maxKeys) 311 if err != nil { 312 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 313 return 314 } 315 316 if err = DecryptETags(ctx, GlobalKMS, listObjectsInfo.Objects); err != nil { 317 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 318 return 319 } 320 321 response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo) 322 323 // Write success response. 324 writeSuccessResponseXML(w, encodeResponseList(response)) 325 }