gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

api-remove.go (15928B)


      1 /*
      2  * MinIO Go Library for Amazon S3 Compatible Cloud Storage
      3  * Copyright 2015-2020 MinIO, Inc.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package minio
     19 
     20 import (
     21 	"bytes"
     22 	"context"
     23 	"encoding/xml"
     24 	"io"
     25 	"net/http"
     26 	"net/url"
     27 	"time"
     28 
     29 	"github.com/minio/minio-go/v7/pkg/s3utils"
     30 )
     31 
     32 //revive:disable
     33 
     34 // Deprecated: BucketOptions will be renamed to RemoveBucketOptions in future versions.
     35 type BucketOptions = RemoveBucketOptions
     36 
     37 //revive:enable
     38 
     39 // RemoveBucketOptions special headers to purge buckets, only
     40 // useful when endpoint is MinIO
     41 type RemoveBucketOptions struct {
     42 	ForceDelete bool
     43 }
     44 
     45 // RemoveBucketWithOptions deletes the bucket name.
     46 //
     47 // All objects (including all object versions and delete markers)
     48 // in the bucket will be deleted forcibly if bucket options set
     49 // ForceDelete to 'true'.
     50 func (c *Client) RemoveBucketWithOptions(ctx context.Context, bucketName string, opts RemoveBucketOptions) error {
     51 	// Input validation.
     52 	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
     53 		return err
     54 	}
     55 
     56 	// Build headers.
     57 	headers := make(http.Header)
     58 	if opts.ForceDelete {
     59 		headers.Set(minIOForceDelete, "true")
     60 	}
     61 
     62 	// Execute DELETE on bucket.
     63 	resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
     64 		bucketName:       bucketName,
     65 		contentSHA256Hex: emptySHA256Hex,
     66 		customHeader:     headers,
     67 	})
     68 	defer closeResponse(resp)
     69 	if err != nil {
     70 		return err
     71 	}
     72 	if resp != nil {
     73 		if resp.StatusCode != http.StatusNoContent {
     74 			return httpRespToErrorResponse(resp, bucketName, "")
     75 		}
     76 	}
     77 
     78 	// Remove the location from cache on a successful delete.
     79 	c.bucketLocCache.Delete(bucketName)
     80 	return nil
     81 }
     82 
     83 // RemoveBucket deletes the bucket name.
     84 //
     85 //	All objects (including all object versions and delete markers).
     86 //	in the bucket must be deleted before successfully attempting this request.
     87 func (c *Client) RemoveBucket(ctx context.Context, bucketName string) error {
     88 	// Input validation.
     89 	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
     90 		return err
     91 	}
     92 	// Execute DELETE on bucket.
     93 	resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
     94 		bucketName:       bucketName,
     95 		contentSHA256Hex: emptySHA256Hex,
     96 	})
     97 	defer closeResponse(resp)
     98 	if err != nil {
     99 		return err
    100 	}
    101 	if resp != nil {
    102 		if resp.StatusCode != http.StatusNoContent {
    103 			return httpRespToErrorResponse(resp, bucketName, "")
    104 		}
    105 	}
    106 
    107 	// Remove the location from cache on a successful delete.
    108 	c.bucketLocCache.Delete(bucketName)
    109 
    110 	return nil
    111 }
    112 
    113 // AdvancedRemoveOptions intended for internal use by replication
    114 type AdvancedRemoveOptions struct {
    115 	ReplicationDeleteMarker bool
    116 	ReplicationStatus       ReplicationStatus
    117 	ReplicationMTime        time.Time
    118 	ReplicationRequest      bool
    119 }
    120 
    121 // RemoveObjectOptions represents options specified by user for RemoveObject call
    122 type RemoveObjectOptions struct {
    123 	ForceDelete      bool
    124 	GovernanceBypass bool
    125 	VersionID        string
    126 	Internal         AdvancedRemoveOptions
    127 }
    128 
    129 // RemoveObject removes an object from a bucket.
    130 func (c *Client) RemoveObject(ctx context.Context, bucketName, objectName string, opts RemoveObjectOptions) error {
    131 	// Input validation.
    132 	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
    133 		return err
    134 	}
    135 	if err := s3utils.CheckValidObjectName(objectName); err != nil {
    136 		return err
    137 	}
    138 
    139 	res := c.removeObject(ctx, bucketName, objectName, opts)
    140 	return res.Err
    141 }
    142 
    143 func (c *Client) removeObject(ctx context.Context, bucketName, objectName string, opts RemoveObjectOptions) RemoveObjectResult {
    144 	// Get resources properly escaped and lined up before
    145 	// using them in http request.
    146 	urlValues := make(url.Values)
    147 
    148 	if opts.VersionID != "" {
    149 		urlValues.Set("versionId", opts.VersionID)
    150 	}
    151 
    152 	// Build headers.
    153 	headers := make(http.Header)
    154 
    155 	if opts.GovernanceBypass {
    156 		// Set the bypass goverenance retention header
    157 		headers.Set(amzBypassGovernance, "true")
    158 	}
    159 	if opts.Internal.ReplicationDeleteMarker {
    160 		headers.Set(minIOBucketReplicationDeleteMarker, "true")
    161 	}
    162 	if !opts.Internal.ReplicationMTime.IsZero() {
    163 		headers.Set(minIOBucketSourceMTime, opts.Internal.ReplicationMTime.Format(time.RFC3339Nano))
    164 	}
    165 	if !opts.Internal.ReplicationStatus.Empty() {
    166 		headers.Set(amzBucketReplicationStatus, string(opts.Internal.ReplicationStatus))
    167 	}
    168 	if opts.Internal.ReplicationRequest {
    169 		headers.Set(minIOBucketReplicationRequest, "true")
    170 	}
    171 	if opts.ForceDelete {
    172 		headers.Set(minIOForceDelete, "true")
    173 	}
    174 	// Execute DELETE on objectName.
    175 	resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
    176 		bucketName:       bucketName,
    177 		objectName:       objectName,
    178 		contentSHA256Hex: emptySHA256Hex,
    179 		queryValues:      urlValues,
    180 		customHeader:     headers,
    181 	})
    182 	defer closeResponse(resp)
    183 	if err != nil {
    184 		return RemoveObjectResult{Err: err}
    185 	}
    186 	if resp != nil {
    187 		// if some unexpected error happened and max retry is reached, we want to let client know
    188 		if resp.StatusCode != http.StatusNoContent {
    189 			err := httpRespToErrorResponse(resp, bucketName, objectName)
    190 			return RemoveObjectResult{Err: err}
    191 		}
    192 	}
    193 
    194 	// DeleteObject always responds with http '204' even for
    195 	// objects which do not exist. So no need to handle them
    196 	// specifically.
    197 	return RemoveObjectResult{
    198 		ObjectName:            objectName,
    199 		ObjectVersionID:       opts.VersionID,
    200 		DeleteMarker:          resp.Header.Get("x-amz-delete-marker") == "true",
    201 		DeleteMarkerVersionID: resp.Header.Get("x-amz-version-id"),
    202 	}
    203 }
    204 
    205 // RemoveObjectError - container of Multi Delete S3 API error
    206 type RemoveObjectError struct {
    207 	ObjectName string
    208 	VersionID  string
    209 	Err        error
    210 }
    211 
    212 // RemoveObjectResult - container of Multi Delete S3 API result
    213 type RemoveObjectResult struct {
    214 	ObjectName      string
    215 	ObjectVersionID string
    216 
    217 	DeleteMarker          bool
    218 	DeleteMarkerVersionID string
    219 
    220 	Err error
    221 }
    222 
    223 // generateRemoveMultiObjects - generate the XML request for remove multi objects request
    224 func generateRemoveMultiObjectsRequest(objects []ObjectInfo) []byte {
    225 	delObjects := []deleteObject{}
    226 	for _, obj := range objects {
    227 		delObjects = append(delObjects, deleteObject{
    228 			Key:       obj.Key,
    229 			VersionID: obj.VersionID,
    230 		})
    231 	}
    232 	xmlBytes, _ := xml.Marshal(deleteMultiObjects{Objects: delObjects, Quiet: false})
    233 	return xmlBytes
    234 }
    235 
    236 // processRemoveMultiObjectsResponse - parse the remove multi objects web service
    237 // and return the success/failure result status for each object
    238 func processRemoveMultiObjectsResponse(body io.Reader, resultCh chan<- RemoveObjectResult) {
    239 	// Parse multi delete XML response
    240 	rmResult := &deleteMultiObjectsResult{}
    241 	err := xmlDecoder(body, rmResult)
    242 	if err != nil {
    243 		resultCh <- RemoveObjectResult{ObjectName: "", Err: err}
    244 		return
    245 	}
    246 
    247 	// Fill deletion that returned success
    248 	for _, obj := range rmResult.DeletedObjects {
    249 		resultCh <- RemoveObjectResult{
    250 			ObjectName: obj.Key,
    251 			// Only filled with versioned buckets
    252 			ObjectVersionID:       obj.VersionID,
    253 			DeleteMarker:          obj.DeleteMarker,
    254 			DeleteMarkerVersionID: obj.DeleteMarkerVersionID,
    255 		}
    256 	}
    257 
    258 	// Fill deletion that returned an error.
    259 	for _, obj := range rmResult.UnDeletedObjects {
    260 		// Version does not exist is not an error ignore and continue.
    261 		switch obj.Code {
    262 		case "InvalidArgument", "NoSuchVersion":
    263 			continue
    264 		}
    265 		resultCh <- RemoveObjectResult{
    266 			ObjectName:      obj.Key,
    267 			ObjectVersionID: obj.VersionID,
    268 			Err: ErrorResponse{
    269 				Code:    obj.Code,
    270 				Message: obj.Message,
    271 			},
    272 		}
    273 	}
    274 }
    275 
    276 // RemoveObjectsOptions represents options specified by user for RemoveObjects call
    277 type RemoveObjectsOptions struct {
    278 	GovernanceBypass bool
    279 }
    280 
    281 // RemoveObjects removes multiple objects from a bucket while
    282 // it is possible to specify objects versions which are received from
    283 // objectsCh. Remove failures are sent back via error channel.
    284 func (c *Client) RemoveObjects(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, opts RemoveObjectsOptions) <-chan RemoveObjectError {
    285 	errorCh := make(chan RemoveObjectError, 1)
    286 
    287 	// Validate if bucket name is valid.
    288 	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
    289 		defer close(errorCh)
    290 		errorCh <- RemoveObjectError{
    291 			Err: err,
    292 		}
    293 		return errorCh
    294 	}
    295 	// Validate objects channel to be properly allocated.
    296 	if objectsCh == nil {
    297 		defer close(errorCh)
    298 		errorCh <- RemoveObjectError{
    299 			Err: errInvalidArgument("Objects channel cannot be nil"),
    300 		}
    301 		return errorCh
    302 	}
    303 
    304 	resultCh := make(chan RemoveObjectResult, 1)
    305 	go c.removeObjects(ctx, bucketName, objectsCh, resultCh, opts)
    306 	go func() {
    307 		defer close(errorCh)
    308 		for res := range resultCh {
    309 			// Send only errors to the error channel
    310 			if res.Err == nil {
    311 				continue
    312 			}
    313 			errorCh <- RemoveObjectError{
    314 				ObjectName: res.ObjectName,
    315 				VersionID:  res.ObjectVersionID,
    316 				Err:        res.Err,
    317 			}
    318 		}
    319 	}()
    320 
    321 	return errorCh
    322 }
    323 
    324 // RemoveObjectsWithResult removes multiple objects from a bucket while
    325 // it is possible to specify objects versions which are received from
    326 // objectsCh. Remove results, successes and failures are sent back via
    327 // RemoveObjectResult channel
    328 func (c *Client) RemoveObjectsWithResult(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, opts RemoveObjectsOptions) <-chan RemoveObjectResult {
    329 	resultCh := make(chan RemoveObjectResult, 1)
    330 
    331 	// Validate if bucket name is valid.
    332 	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
    333 		defer close(resultCh)
    334 		resultCh <- RemoveObjectResult{
    335 			Err: err,
    336 		}
    337 		return resultCh
    338 	}
    339 	// Validate objects channel to be properly allocated.
    340 	if objectsCh == nil {
    341 		defer close(resultCh)
    342 		resultCh <- RemoveObjectResult{
    343 			Err: errInvalidArgument("Objects channel cannot be nil"),
    344 		}
    345 		return resultCh
    346 	}
    347 
    348 	go c.removeObjects(ctx, bucketName, objectsCh, resultCh, opts)
    349 	return resultCh
    350 }
    351 
    352 // Return true if the character is within the allowed characters in an XML 1.0 document
    353 // The list of allowed characters can be found here: https://www.w3.org/TR/xml/#charsets
    354 func validXMLChar(r rune) (ok bool) {
    355 	return r == 0x09 ||
    356 		r == 0x0A ||
    357 		r == 0x0D ||
    358 		r >= 0x20 && r <= 0xD7FF ||
    359 		r >= 0xE000 && r <= 0xFFFD ||
    360 		r >= 0x10000 && r <= 0x10FFFF
    361 }
    362 
    363 func hasInvalidXMLChar(str string) bool {
    364 	for _, s := range str {
    365 		if !validXMLChar(s) {
    366 			return true
    367 		}
    368 	}
    369 	return false
    370 }
    371 
    372 // Generate and call MultiDelete S3 requests based on entries received from objectsCh
    373 func (c *Client) removeObjects(ctx context.Context, bucketName string, objectsCh <-chan ObjectInfo, resultCh chan<- RemoveObjectResult, opts RemoveObjectsOptions) {
    374 	maxEntries := 1000
    375 	finish := false
    376 	urlValues := make(url.Values)
    377 	urlValues.Set("delete", "")
    378 
    379 	// Close result channel when Multi delete finishes.
    380 	defer close(resultCh)
    381 
    382 	// Loop over entries by 1000 and call MultiDelete requests
    383 	for {
    384 		if finish {
    385 			break
    386 		}
    387 		count := 0
    388 		var batch []ObjectInfo
    389 
    390 		// Try to gather 1000 entries
    391 		for object := range objectsCh {
    392 			if hasInvalidXMLChar(object.Key) {
    393 				// Use single DELETE so the object name will be in the request URL instead of the multi-delete XML document.
    394 				removeResult := c.removeObject(ctx, bucketName, object.Key, RemoveObjectOptions{
    395 					VersionID:        object.VersionID,
    396 					GovernanceBypass: opts.GovernanceBypass,
    397 				})
    398 				if err := removeResult.Err; err != nil {
    399 					// Version does not exist is not an error ignore and continue.
    400 					switch ToErrorResponse(err).Code {
    401 					case "InvalidArgument", "NoSuchVersion":
    402 						continue
    403 					}
    404 					resultCh <- removeResult
    405 				}
    406 
    407 				resultCh <- removeResult
    408 				continue
    409 			}
    410 
    411 			batch = append(batch, object)
    412 			if count++; count >= maxEntries {
    413 				break
    414 			}
    415 		}
    416 		if count == 0 {
    417 			// Multi Objects Delete API doesn't accept empty object list, quit immediately
    418 			break
    419 		}
    420 		if count < maxEntries {
    421 			// We didn't have 1000 entries, so this is the last batch
    422 			finish = true
    423 		}
    424 
    425 		// Build headers.
    426 		headers := make(http.Header)
    427 		if opts.GovernanceBypass {
    428 			// Set the bypass goverenance retention header
    429 			headers.Set(amzBypassGovernance, "true")
    430 		}
    431 
    432 		// Generate remove multi objects XML request
    433 		removeBytes := generateRemoveMultiObjectsRequest(batch)
    434 		// Execute GET on bucket to list objects.
    435 		resp, err := c.executeMethod(ctx, http.MethodPost, requestMetadata{
    436 			bucketName:       bucketName,
    437 			queryValues:      urlValues,
    438 			contentBody:      bytes.NewReader(removeBytes),
    439 			contentLength:    int64(len(removeBytes)),
    440 			contentMD5Base64: sumMD5Base64(removeBytes),
    441 			contentSHA256Hex: sum256Hex(removeBytes),
    442 			customHeader:     headers,
    443 		})
    444 		if resp != nil {
    445 			if resp.StatusCode != http.StatusOK {
    446 				e := httpRespToErrorResponse(resp, bucketName, "")
    447 				resultCh <- RemoveObjectResult{ObjectName: "", Err: e}
    448 			}
    449 		}
    450 		if err != nil {
    451 			for _, b := range batch {
    452 				resultCh <- RemoveObjectResult{
    453 					ObjectName:      b.Key,
    454 					ObjectVersionID: b.VersionID,
    455 					Err:             err,
    456 				}
    457 			}
    458 			continue
    459 		}
    460 
    461 		// Process multiobjects remove xml response
    462 		processRemoveMultiObjectsResponse(resp.Body, resultCh)
    463 
    464 		closeResponse(resp)
    465 	}
    466 }
    467 
    468 // RemoveIncompleteUpload aborts an partially uploaded object.
    469 func (c *Client) RemoveIncompleteUpload(ctx context.Context, bucketName, objectName string) error {
    470 	// Input validation.
    471 	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
    472 		return err
    473 	}
    474 	if err := s3utils.CheckValidObjectName(objectName); err != nil {
    475 		return err
    476 	}
    477 	// Find multipart upload ids of the object to be aborted.
    478 	uploadIDs, err := c.findUploadIDs(ctx, bucketName, objectName)
    479 	if err != nil {
    480 		return err
    481 	}
    482 
    483 	for _, uploadID := range uploadIDs {
    484 		// abort incomplete multipart upload, based on the upload id passed.
    485 		err := c.abortMultipartUpload(ctx, bucketName, objectName, uploadID)
    486 		if err != nil {
    487 			return err
    488 		}
    489 	}
    490 
    491 	return nil
    492 }
    493 
    494 // abortMultipartUpload aborts a multipart upload for the given
    495 // uploadID, all previously uploaded parts are deleted.
    496 func (c *Client) abortMultipartUpload(ctx context.Context, bucketName, objectName, uploadID string) error {
    497 	// Input validation.
    498 	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
    499 		return err
    500 	}
    501 	if err := s3utils.CheckValidObjectName(objectName); err != nil {
    502 		return err
    503 	}
    504 
    505 	// Initialize url queries.
    506 	urlValues := make(url.Values)
    507 	urlValues.Set("uploadId", uploadID)
    508 
    509 	// Execute DELETE on multipart upload.
    510 	resp, err := c.executeMethod(ctx, http.MethodDelete, requestMetadata{
    511 		bucketName:       bucketName,
    512 		objectName:       objectName,
    513 		queryValues:      urlValues,
    514 		contentSHA256Hex: emptySHA256Hex,
    515 	})
    516 	defer closeResponse(resp)
    517 	if err != nil {
    518 		return err
    519 	}
    520 	if resp != nil {
    521 		if resp.StatusCode != http.StatusNoContent {
    522 			// Abort has no response body, handle it for any errors.
    523 			var errorResponse ErrorResponse
    524 			switch resp.StatusCode {
    525 			case http.StatusNotFound:
    526 				// This is needed specifically for abort and it cannot
    527 				// be converged into default case.
    528 				errorResponse = ErrorResponse{
    529 					Code:       "NoSuchUpload",
    530 					Message:    "The specified multipart upload does not exist.",
    531 					BucketName: bucketName,
    532 					Key:        objectName,
    533 					RequestID:  resp.Header.Get("x-amz-request-id"),
    534 					HostID:     resp.Header.Get("x-amz-id-2"),
    535 					Region:     resp.Header.Get("x-amz-bucket-region"),
    536 				}
    537 			default:
    538 				return httpRespToErrorResponse(resp, bucketName, objectName)
    539 			}
    540 			return errorResponse
    541 		}
    542 	}
    543 	return nil
    544 }