2021-08-19 14:06:43 +00:00
import io
2021-02-10 09:06:03 +00:00
import os
2013-05-14 17:47:24 +00:00
import re
2023-04-20 16:47:39 +00:00
from typing import Any , Dict , List , Iterator , Union , Tuple , Optional , Type
2013-02-27 06:12:11 +00:00
2022-06-17 10:21:06 +00:00
import urllib . parse
2022-01-18 15:18:57 +00:00
from moto import settings
2022-03-31 10:47:29 +00:00
from moto . core . utils import (
extract_region_from_aws_authorization ,
str_to_rfc_1123_datetime ,
)
2021-07-26 06:40:39 +00:00
from urllib . parse import parse_qs , urlparse , unquote , urlencode , urlunparse
2016-11-24 01:05:34 +00:00
2015-06-03 03:11:23 +00:00
import xmltodict
2014-11-27 15:43:10 +00:00
2023-04-20 16:47:39 +00:00
from moto . core . common_types import TYPE_RESPONSE
2022-06-09 17:40:22 +00:00
from moto . core . responses import BaseResponse
2018-09-06 22:15:27 +00:00
from moto . core . utils import path_url
2013-02-18 21:09:40 +00:00
2018-01-03 04:47:57 +00:00
from moto . s3bucket_path . utils import (
bucket_name_from_url as bucketpath_bucket_name_from_url ,
parse_key_name as bucketpath_parse_key_name ,
2019-10-31 15:44:26 +00:00
)
2022-09-28 09:35:12 +00:00
from moto . utilities . aws_headers import amzn_request_id
2019-10-31 15:44:26 +00:00
2018-01-03 04:47:57 +00:00
from . exceptions import (
BucketAlreadyExists ,
2022-05-12 13:59:47 +00:00
BucketAccessDeniedError ,
2021-08-17 05:16:59 +00:00
BucketMustHaveLockeEnabled ,
2020-04-01 14:35:25 +00:00
DuplicateTagKeys ,
2021-08-17 05:16:59 +00:00
InvalidContentMD5 ,
2020-12-13 13:38:25 +00:00
InvalidContinuationToken ,
2018-01-03 04:47:57 +00:00
S3ClientError ,
MissingBucket ,
MissingKey ,
2021-01-13 16:06:09 +00:00
MissingVersion ,
2021-08-28 06:38:16 +00:00
InvalidMaxPartArgument ,
2021-11-23 19:47:48 +00:00
InvalidMaxPartNumberArgument ,
NotAnIntegerException ,
2018-01-03 04:47:57 +00:00
InvalidPartOrder ,
MalformedXML ,
2019-08-05 15:34:39 +00:00
MalformedACLError ,
2020-02-02 10:36:51 +00:00
IllegalLocationConstraintException ,
2019-08-05 15:34:39 +00:00
InvalidNotificationARN ,
InvalidNotificationEvent ,
2021-11-09 22:49:37 +00:00
S3AclAndGrantError ,
2021-11-02 23:02:14 +00:00
InvalidObjectState ,
2019-08-05 15:34:39 +00:00
ObjectNotInActiveTierError ,
2020-03-31 00:23:33 +00:00
NoSystemTags ,
2020-09-11 09:17:39 +00:00
PreconditionFailed ,
2020-10-28 14:22:18 +00:00
InvalidRange ,
2021-08-17 05:16:59 +00:00
LockNotEnabled ,
2023-03-03 22:40:55 +00:00
AccessForbidden ,
2019-10-31 15:44:26 +00:00
)
2023-03-16 11:56:20 +00:00
from . models import s3_backends , S3Backend
2023-04-20 16:47:39 +00:00
from . models import get_canned_acl , FakeGrantee , FakeGrant , FakeAcl , FakeKey , FakeBucket
2023-03-21 16:55:19 +00:00
from . select_object_content import serialize_select
2022-12-06 23:03:28 +00:00
from . utils import (
bucket_name_from_url ,
metadata_from_headers ,
parse_region_from_url ,
compute_checksum ,
2023-02-27 15:44:30 +00:00
ARCHIVE_STORAGE_CLASSES ,
2023-03-03 22:40:55 +00:00
cors_matches_origin ,
2022-12-06 23:03:28 +00:00
)
2014-03-26 17:15:08 +00:00
from xml . dom import minidom
2013-02-19 02:22:03 +00:00
2018-01-31 00:10:43 +00:00
2014-12-11 01:20:43 +00:00
DEFAULT_REGION_NAME = " us-east-1 "
2019-07-04 14:38:43 +00:00
ACTION_MAP = {
" BUCKET " : {
2021-09-25 17:25:27 +00:00
" HEAD " : { " DEFAULT " : " HeadBucket " } ,
2019-07-04 14:38:43 +00:00
" GET " : {
" uploads " : " ListBucketMultipartUploads " ,
" location " : " GetBucketLocation " ,
" lifecycle " : " GetLifecycleConfiguration " ,
" versioning " : " GetBucketVersioning " ,
" policy " : " GetBucketPolicy " ,
" website " : " GetBucketWebsite " ,
" acl " : " GetBucketAcl " ,
" tagging " : " GetBucketTagging " ,
" logging " : " GetBucketLogging " ,
" cors " : " GetBucketCORS " ,
" notification " : " GetBucketNotification " ,
" accelerate " : " GetAccelerateConfiguration " ,
" versions " : " ListBucketVersions " ,
2019-12-10 01:38:26 +00:00
" public_access_block " : " GetPublicAccessBlock " ,
2019-07-04 14:38:43 +00:00
" DEFAULT " : " ListBucket " ,
} ,
" PUT " : {
" lifecycle " : " PutLifecycleConfiguration " ,
" versioning " : " PutBucketVersioning " ,
" policy " : " PutBucketPolicy " ,
" website " : " PutBucketWebsite " ,
" acl " : " PutBucketAcl " ,
" tagging " : " PutBucketTagging " ,
" logging " : " PutBucketLogging " ,
" cors " : " PutBucketCORS " ,
" notification " : " PutBucketNotification " ,
" accelerate " : " PutAccelerateConfiguration " ,
2019-12-10 01:38:26 +00:00
" public_access_block " : " PutPublicAccessBlock " ,
2019-07-04 14:38:43 +00:00
" DEFAULT " : " CreateBucket " ,
} ,
" DELETE " : {
" lifecycle " : " PutLifecycleConfiguration " ,
" policy " : " DeleteBucketPolicy " ,
2021-07-26 14:21:17 +00:00
" website " : " DeleteBucketWebsite " ,
2019-07-04 14:38:43 +00:00
" tagging " : " PutBucketTagging " ,
" cors " : " PutBucketCORS " ,
2019-12-10 01:38:26 +00:00
" public_access_block " : " DeletePublicAccessBlock " ,
2019-07-04 14:38:43 +00:00
" DEFAULT " : " DeleteBucket " ,
} ,
} ,
" KEY " : {
2021-08-29 13:49:05 +00:00
" HEAD " : { " DEFAULT " : " HeadObject " } ,
2019-07-04 14:38:43 +00:00
" GET " : {
" uploadId " : " ListMultipartUploadParts " ,
" acl " : " GetObjectAcl " ,
" tagging " : " GetObjectTagging " ,
" versionId " : " GetObjectVersion " ,
" DEFAULT " : " GetObject " ,
} ,
" PUT " : {
" acl " : " PutObjectAcl " ,
" tagging " : " PutObjectTagging " ,
" DEFAULT " : " PutObject " ,
} ,
" DELETE " : {
" uploadId " : " AbortMultipartUpload " ,
" versionId " : " DeleteObjectVersion " ,
2021-07-29 13:50:24 +00:00
" DEFAULT " : " DeleteObject " ,
2019-07-04 14:38:43 +00:00
} ,
" POST " : {
" uploads " : " PutObject " ,
" restore " : " RestoreObject " ,
" uploadId " : " PutObject " ,
2023-03-21 16:55:19 +00:00
" select " : " SelectObject " ,
2019-07-04 14:38:43 +00:00
} ,
2019-10-31 15:44:26 +00:00
} ,
2020-02-14 02:01:44 +00:00
" CONTROL " : {
" GET " : { " publicAccessBlock " : " GetPublicAccessBlock " } ,
" PUT " : { " publicAccessBlock " : " PutPublicAccessBlock " } ,
" DELETE " : { " publicAccessBlock " : " DeletePublicAccessBlock " } ,
} ,
2019-07-04 14:38:43 +00:00
}
2018-05-03 09:10:17 +00:00
2023-04-20 16:47:39 +00:00
def parse_key_name ( pth : str ) - > str :
2020-04-20 18:54:31 +00:00
# strip the first '/' left by urlparse
2020-04-25 13:10:23 +00:00
return pth [ 1 : ] if pth . startswith ( " / " ) else pth
2013-10-28 20:43:25 +00:00
2022-06-09 17:40:22 +00:00
class S3Response ( BaseResponse ) :
2023-04-20 16:47:39 +00:00
def __init__ ( self ) - > None :
2022-08-13 09:49:43 +00:00
super ( ) . __init__ ( service_name = " s3 " )
2022-06-09 17:40:22 +00:00
@property
2023-03-16 11:56:20 +00:00
def backend ( self ) - > S3Backend :
2022-08-13 09:49:43 +00:00
return s3_backends [ self . current_account ] [ " global " ]
2013-10-28 20:43:25 +00:00
2016-12-03 23:13:24 +00:00
@property
2023-04-20 16:47:39 +00:00
def should_autoescape ( self ) - > bool :
2016-12-03 23:13:24 +00:00
return True
2023-04-20 16:47:39 +00:00
def all_buckets ( self ) - > str :
2019-07-16 14:27:50 +00:00
self . data [ " Action " ] = " ListAllMyBuckets "
2019-07-26 19:05:04 +00:00
self . _authenticate_and_authorize_s3_action ( )
2019-07-16 14:27:50 +00:00
2013-10-28 20:43:25 +00:00
# No bucket specified. Listing all buckets
2021-08-21 14:05:40 +00:00
all_buckets = self . backend . list_buckets ( )
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_ALL_BUCKETS )
2013-10-28 20:43:25 +00:00
return template . render ( buckets = all_buckets )
2023-04-20 16:47:39 +00:00
def subdomain_based_buckets ( self , request : Any ) - > bool :
2021-11-17 21:02:14 +00:00
if settings . S3_IGNORE_SUBDOMAIN_BUCKETNAME :
2021-03-26 16:51:19 +00:00
return False
2016-01-17 22:19:53 +00:00
host = request . headers . get ( " host " , request . headers . get ( " Host " ) )
2017-02-16 03:35:45 +00:00
if not host :
host = urlparse ( request . url ) . netloc
2016-11-24 01:05:34 +00:00
2021-11-17 21:02:14 +00:00
custom_endpoints = settings . get_s3_custom_endpoints ( )
if (
host
and custom_endpoints
and any ( [ host in endpoint for endpoint in custom_endpoints ] )
) :
# Default to path-based buckets for S3-compatible SDKs (Ceph, DigitalOcean Spaces, etc)
return False
2018-03-21 16:33:09 +00:00
if (
not host
or host . startswith ( " localhost " )
or host . startswith ( " localstack " )
2021-11-02 22:00:15 +00:00
or host . startswith ( " host.docker.internal " )
2017-11-15 19:37:39 +00:00
or re . match ( r " ^[^.]+$ " , host )
2020-02-17 23:38:53 +00:00
or re . match ( r " ^.* \ .svc \ .cluster \ .local:? \ d*$ " , host )
2017-11-15 19:37:39 +00:00
) :
2018-03-21 16:33:09 +00:00
# Default to path-based buckets for (1) localhost, (2) localstack hosts (e.g. localstack.dev),
# (3) local host names that do not contain a "." (e.g., Docker container host names), or
# (4) kubernetes host names
2015-11-27 19:43:03 +00:00
return False
2016-01-17 22:19:53 +00:00
2016-11-24 01:05:34 +00:00
match = re . match ( r " ^([^ \ [ \ ]:]+)(: \ d+)?$ " , host )
if match :
2016-12-03 23:15:24 +00:00
match = re . match (
r " ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)( \ .|$)) {4} " , match . groups ( ) [ 0 ]
)
if match :
2016-11-24 01:05:34 +00:00
return False
match = re . match ( r " ^ \ [(.+) \ ](: \ d+)?$ " , host )
if match :
2018-01-03 04:47:57 +00:00
match = re . match (
r " ^(((?=.*(::))(?!.* \ 3.+ \ 3)) \ 3?|[ \ dA-F] { 1,4}:)([ \ dA-F] { 1,4}( \ 3|: \ b)| \ 2) {5} (([ \ dA-F] { 1,4}( \ 3|: \ b|$)| \ 2) {2} |(((2[0-4]|1 \ d|[1-9])? \ d|25[0-5]) \ .? \ b) {4} ) \ Z " ,
match . groups ( ) [ 0 ] ,
re . IGNORECASE ,
)
2016-12-03 23:15:24 +00:00
if match :
2016-11-24 01:05:34 +00:00
return False
2017-02-24 02:37:43 +00:00
path_based = host == " s3.amazonaws.com " or re . match (
r " s3[ \ . \ -]([^.]*) \ .amazonaws \ .com " , host
)
2016-01-17 22:19:53 +00:00
return not path_based
2015-11-27 18:49:44 +00:00
2023-04-20 16:47:39 +00:00
def is_delete_keys ( self ) - > bool :
2022-10-29 15:02:46 +00:00
qs = parse_qs ( urlparse ( self . path ) . query , keep_blank_values = True )
return " delete " in qs
2015-11-27 19:43:03 +00:00
2023-04-20 16:47:39 +00:00
def parse_bucket_name_from_url ( self , request : Any , url : str ) - > str :
2015-11-27 19:43:03 +00:00
if self . subdomain_based_buckets ( request ) :
2023-04-20 16:47:39 +00:00
return bucket_name_from_url ( url ) # type: ignore
2015-11-27 19:43:03 +00:00
else :
2023-04-20 16:47:39 +00:00
return bucketpath_bucket_name_from_url ( url ) # type: ignore
2015-11-27 18:49:44 +00:00
2023-04-20 16:47:39 +00:00
def parse_key_name ( self , request : Any , url : str ) - > str :
2015-11-27 19:43:03 +00:00
if self . subdomain_based_buckets ( request ) :
2015-11-27 18:49:44 +00:00
return parse_key_name ( url )
2015-11-27 19:43:03 +00:00
else :
return bucketpath_parse_key_name ( url )
2015-11-27 18:49:44 +00:00
2023-04-20 16:47:39 +00:00
def ambiguous_response (
self , request : Any , full_url : str , headers : Any
) - > TYPE_RESPONSE :
2015-11-27 18:49:44 +00:00
# Depending on which calling format the client is using, we don't know
# if this is a bucket or key request so we have to check
2015-11-27 19:43:03 +00:00
if self . subdomain_based_buckets ( request ) :
2022-01-18 20:10:22 +00:00
return self . key_response ( request , full_url , headers )
2015-11-27 19:43:03 +00:00
else :
2015-11-27 18:49:44 +00:00
# Using path-based buckets
return self . bucket_response ( request , full_url , headers )
2021-08-28 07:32:14 +00:00
@amzn_request_id
2023-04-20 16:47:39 +00:00
def bucket_response ( self , request : Any , full_url : str , headers : Any ) - > TYPE_RESPONSE : # type: ignore
2022-06-09 17:40:22 +00:00
self . setup_class ( request , full_url , headers , use_raw_body = True )
2023-01-16 23:36:08 +00:00
bucket_name = self . parse_bucket_name_from_url ( request , full_url )
self . backend . log_incoming_request ( request , bucket_name )
2014-07-09 01:20:29 +00:00
try :
2022-03-11 21:28:45 +00:00
response = self . _bucket_response ( request , full_url )
2015-02-10 13:33:18 +00:00
except S3ClientError as s3error :
2017-02-17 03:51:04 +00:00
response = s3error . code , { } , s3error . description
2014-07-09 01:20:29 +00:00
2019-07-04 14:38:43 +00:00
return self . _send_response ( response )
@staticmethod
2023-04-20 16:47:39 +00:00
def _send_response ( response : Any ) - > TYPE_RESPONSE : # type: ignore
2021-07-26 06:40:39 +00:00
if isinstance ( response , str ) :
2017-02-17 03:51:04 +00:00
return 200 , { } , response . encode ( " utf-8 " )
2013-05-17 09:43:09 +00:00
else :
2013-10-28 20:43:25 +00:00
status_code , headers , response_content = response
2021-07-26 06:40:39 +00:00
if not isinstance ( response_content , bytes ) :
2017-11-06 21:39:08 +00:00
response_content = response_content . encode ( " utf-8 " )
return status_code , headers , response_content
2013-10-28 20:43:25 +00:00
2023-04-20 16:47:39 +00:00
def _bucket_response (
self , request : Any , full_url : str
) - > Union [ str , TYPE_RESPONSE ] :
2022-11-22 23:41:02 +00:00
querystring = self . _get_querystring ( request , full_url )
2013-10-28 20:43:25 +00:00
method = request . method
2022-03-31 10:47:29 +00:00
region_name = parse_region_from_url ( full_url , use_default_region = False )
if region_name is None :
region_name = extract_region_from_aws_authorization (
request . headers . get ( " Authorization " , " " )
)
region_name = region_name or DEFAULT_REGION_NAME
2013-10-28 20:43:25 +00:00
2015-11-27 18:49:44 +00:00
bucket_name = self . parse_bucket_name_from_url ( request , full_url )
2013-10-28 20:43:25 +00:00
if not bucket_name :
# If no bucket specified, list all buckets
return self . all_buckets ( )
2019-07-04 14:38:43 +00:00
self . data [ " BucketName " ] = bucket_name
2013-10-28 20:43:25 +00:00
2014-02-10 11:18:06 +00:00
if method == " HEAD " :
2021-09-25 17:25:27 +00:00
return self . _bucket_response_head ( bucket_name , querystring )
2014-02-10 11:18:06 +00:00
elif method == " GET " :
2019-07-16 14:27:50 +00:00
return self . _bucket_response_get ( bucket_name , querystring )
2013-10-28 20:43:25 +00:00
elif method == " PUT " :
2019-07-16 14:27:50 +00:00
return self . _bucket_response_put (
2022-06-09 17:40:22 +00:00
request , region_name , bucket_name , querystring
2019-07-16 14:27:50 +00:00
)
2013-10-28 20:43:25 +00:00
elif method == " DELETE " :
2022-03-11 21:28:45 +00:00
return self . _bucket_response_delete ( bucket_name , querystring )
2013-10-28 20:43:25 +00:00
elif method == " POST " :
2022-06-09 17:40:22 +00:00
return self . _bucket_response_post ( request , bucket_name )
2021-10-30 10:02:30 +00:00
elif method == " OPTIONS " :
2023-03-03 22:40:55 +00:00
return self . _response_options ( request . headers , bucket_name )
2014-03-30 15:50:36 +00:00
else :
2017-02-24 02:37:43 +00:00
raise NotImplementedError (
2022-11-21 19:21:34 +00:00
f " Method { method } has not been implemented in the S3 backend yet "
2019-10-31 15:44:26 +00:00
)
2013-10-28 20:43:25 +00:00
2019-07-04 14:38:43 +00:00
@staticmethod
2023-04-20 16:47:39 +00:00
def _get_querystring ( request : Any , full_url : str ) - > Dict [ str , Any ] : # type: ignore[misc]
2022-11-22 23:41:02 +00:00
# Flask's Request has the querystring already parsed
# In ServerMode, we can use this, instead of manually parsing this
if hasattr ( request , " args " ) :
query_dict = dict ( )
for key , val in dict ( request . args ) . items ( ) :
# The parse_qs-method returns List[str, List[Any]]
# Ensure that we confirm to the same response-type here
query_dict [ key ] = val if isinstance ( val , list ) else [ val ]
return query_dict
2019-07-04 14:38:43 +00:00
parsed_url = urlparse ( full_url )
2022-05-05 11:06:31 +00:00
# full_url can be one of two formats, depending on the version of werkzeug used:
# http://foobaz.localhost:5000/?prefix=bar%2Bbaz
# http://foobaz.localhost:5000/?prefix=bar+baz
# Werkzeug helpfully encodes the plus-sign for us, from >= 2.1.0
# However, the `parse_qs` method will (correctly) replace '+' with a space
#
# Workaround - manually reverse the encoding.
# Keep the + encoded, ensuring that parse_qsl doesn't replace it, and parse_qsl will unquote it afterwards
qs = ( parsed_url . query or " " ) . replace ( " + " , " % 2B " )
2023-04-20 16:47:39 +00:00
return parse_qs ( qs , keep_blank_values = True )
2019-07-04 14:38:43 +00:00
2023-04-20 16:47:39 +00:00
def _bucket_response_head (
self , bucket_name : str , querystring : Dict [ str , Any ]
) - > TYPE_RESPONSE :
2021-09-25 17:25:27 +00:00
self . _set_action ( " BUCKET " , " HEAD " , querystring )
self . _authenticate_and_authorize_s3_action ( )
2017-07-20 00:18:31 +00:00
try :
2023-03-23 10:13:51 +00:00
bucket = self . backend . head_bucket ( bucket_name )
2017-07-20 00:18:31 +00:00
except MissingBucket :
# Unless we do this, boto3 does not raise ClientError on
# HEAD (which the real API responds with), and instead
# raises NoSuchBucket, leading to inconsistency in
# error response between real and mocked responses.
2018-01-30 21:48:04 +00:00
return 404 , { } , " "
2023-03-23 10:13:51 +00:00
return 200 , { " x-amz-bucket-region " : bucket . region_name } , " "
2013-09-27 17:47:32 +00:00
2023-04-20 16:47:39 +00:00
def _set_cors_headers ( self , headers : Dict [ str , str ] , bucket : FakeBucket ) - > None :
2021-10-30 10:02:30 +00:00
"""
TODO : smarter way of matching the right CORS rule :
See https : / / docs . aws . amazon . com / AmazonS3 / latest / userguide / cors . html
" When Amazon S3 receives a preflight request from a browser, it evaluates
the CORS configuration for the bucket and uses the first CORSRule rule
that matches the incoming browser request to enable a cross - origin request . "
This here just uses all rules and the last rule will override the previous ones
if they are re - defining the same headers .
"""
def _to_string ( header : Union [ List [ str ] , str ] ) - > str :
# We allow list and strs in header values. Transform lists in comma-separated strings
if isinstance ( header , list ) :
return " , " . join ( header )
return header
for cors_rule in bucket . cors :
if cors_rule . allowed_methods is not None :
2022-06-09 17:40:22 +00:00
self . response_headers [ " Access-Control-Allow-Methods " ] = _to_string (
2021-10-30 10:02:30 +00:00
cors_rule . allowed_methods
)
if cors_rule . allowed_origins is not None :
2023-03-03 22:40:55 +00:00
origin = headers . get ( " Origin " )
2023-04-20 16:47:39 +00:00
if cors_matches_origin ( origin , cors_rule . allowed_origins ) : # type: ignore
self . response_headers [ " Access-Control-Allow-Origin " ] = origin # type: ignore
2023-03-03 22:40:55 +00:00
else :
raise AccessForbidden (
" CORSResponse: This CORS request is not allowed. This is usually because the evalution of Origin, request method / Access-Control-Request-Method or Access-Control-Request-Headers are not whitelisted by the resource ' s CORS spec. "
)
2021-10-30 10:02:30 +00:00
if cors_rule . allowed_headers is not None :
2022-06-09 17:40:22 +00:00
self . response_headers [ " Access-Control-Allow-Headers " ] = _to_string (
2021-10-30 10:02:30 +00:00
cors_rule . allowed_headers
)
if cors_rule . exposed_headers is not None :
2022-06-09 17:40:22 +00:00
self . response_headers [ " Access-Control-Expose-Headers " ] = _to_string (
2021-10-30 10:02:30 +00:00
cors_rule . exposed_headers
)
if cors_rule . max_age_seconds is not None :
2022-06-09 17:40:22 +00:00
self . response_headers [ " Access-Control-Max-Age " ] = _to_string (
2021-10-30 10:02:30 +00:00
cors_rule . max_age_seconds
)
2023-04-20 16:47:39 +00:00
def _response_options (
self , headers : Dict [ str , str ] , bucket_name : str
) - > TYPE_RESPONSE :
2021-10-30 10:02:30 +00:00
# Return 200 with the headers from the bucket CORS configuration
self . _authenticate_and_authorize_s3_action ( )
try :
bucket = self . backend . head_bucket ( bucket_name )
except MissingBucket :
2023-04-20 16:47:39 +00:00
# AWS S3 seems to return 403 on OPTIONS and 404 on GET/HEAD
return 403 , { } , " "
2021-10-30 10:02:30 +00:00
2023-03-03 22:40:55 +00:00
self . _set_cors_headers ( headers , bucket )
2021-10-30 10:02:30 +00:00
2022-06-09 17:40:22 +00:00
return 200 , self . response_headers , " "
2021-10-30 10:02:30 +00:00
2023-04-20 16:47:39 +00:00
def _bucket_response_get (
self , bucket_name : str , querystring : Dict [ str , Any ]
) - > Union [ str , TYPE_RESPONSE ] :
2019-07-04 14:38:43 +00:00
self . _set_action ( " BUCKET " , " GET " , querystring )
2019-07-26 19:05:04 +00:00
self . _authenticate_and_authorize_s3_action ( )
2019-07-04 14:38:43 +00:00
2021-08-17 05:16:59 +00:00
if " object-lock " in querystring :
2021-08-21 14:05:40 +00:00
(
lock_enabled ,
mode ,
days ,
years ,
) = self . backend . get_object_lock_configuration ( bucket_name )
2021-08-17 05:16:59 +00:00
template = self . response_template ( S3_BUCKET_LOCK_CONFIGURATION )
return template . render (
2021-08-21 14:05:40 +00:00
lock_enabled = lock_enabled , mode = mode , days = days , years = years
2021-08-17 05:16:59 +00:00
)
2014-04-02 16:03:40 +00:00
if " uploads " in querystring :
2015-01-29 16:22:06 +00:00
for unsup in ( " delimiter " , " max-uploads " ) :
2014-04-02 16:03:40 +00:00
if unsup in querystring :
2017-02-24 02:37:43 +00:00
raise NotImplementedError (
2022-11-21 19:21:34 +00:00
f " Listing multipart uploads with { unsup } has not been implemented yet. "
2019-10-31 15:44:26 +00:00
)
2017-02-24 02:37:43 +00:00
multiparts = list ( self . backend . get_all_multiparts ( bucket_name ) . values ( ) )
2015-01-29 16:22:06 +00:00
if " prefix " in querystring :
prefix = querystring . get ( " prefix " , [ None ] ) [ 0 ]
2017-02-24 02:37:43 +00:00
multiparts = [
upload
for upload in multiparts
if upload . key_name . startswith ( prefix )
]
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_ALL_MULTIPARTS )
2022-08-13 09:49:43 +00:00
return template . render (
bucket_name = bucket_name ,
uploads = multiparts ,
account_id = self . current_account ,
)
2014-12-11 01:44:00 +00:00
elif " location " in querystring :
2023-04-20 16:47:39 +00:00
location : Optional [ str ] = self . backend . get_bucket_location ( bucket_name )
2014-12-12 21:05:46 +00:00
template = self . response_template ( S3_BUCKET_LOCATION )
2018-11-20 18:50:42 +00:00
# us-east-1 is different - returns a None location
if location == DEFAULT_REGION_NAME :
location = None
return template . render ( location = location )
2015-06-03 03:11:23 +00:00
elif " lifecycle " in querystring :
2021-08-21 14:05:40 +00:00
rules = self . backend . get_bucket_lifecycle ( bucket_name )
if not rules :
2017-09-07 18:30:05 +00:00
template = self . response_template ( S3_NO_LIFECYCLE )
return 404 , { } , template . render ( bucket_name = bucket_name )
2017-02-24 02:37:43 +00:00
template = self . response_template ( S3_BUCKET_LIFECYCLE_CONFIGURATION )
2021-08-21 14:05:40 +00:00
return template . render ( rules = rules )
2014-06-27 17:34:00 +00:00
elif " versioning " in querystring :
versioning = self . backend . get_bucket_versioning ( bucket_name )
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_BUCKET_GET_VERSIONING )
2015-11-04 23:55:41 +00:00
return template . render ( status = versioning )
2015-07-23 21:33:52 +00:00
elif " policy " in querystring :
policy = self . backend . get_bucket_policy ( bucket_name )
if not policy :
template = self . response_template ( S3_NO_POLICY )
2017-02-17 03:51:04 +00:00
return 404 , { } , template . render ( bucket_name = bucket_name )
return 200 , { } , policy
2015-11-04 23:55:41 +00:00
elif " website " in querystring :
2017-02-24 02:37:43 +00:00
website_configuration = self . backend . get_bucket_website_configuration (
bucket_name
)
2017-09-07 18:30:05 +00:00
if not website_configuration :
template = self . response_template ( S3_NO_BUCKET_WEBSITE_CONFIG )
return 404 , { } , template . render ( bucket_name = bucket_name )
2023-04-20 16:47:39 +00:00
return 200 , { } , website_configuration # type: ignore
2015-11-12 01:26:29 +00:00
elif " acl " in querystring :
2021-08-21 14:05:40 +00:00
acl = self . backend . get_bucket_acl ( bucket_name )
2015-11-12 01:26:29 +00:00
template = self . response_template ( S3_OBJECT_ACL_RESPONSE )
2021-08-21 14:05:40 +00:00
return template . render ( acl = acl )
2017-09-07 18:30:05 +00:00
elif " tagging " in querystring :
2020-06-06 12:15:50 +00:00
tags = self . backend . get_bucket_tagging ( bucket_name ) [ " Tags " ]
2017-09-07 18:30:05 +00:00
# "Special Error" if no tags:
2020-03-31 10:10:38 +00:00
if len ( tags ) == 0 :
2017-09-07 18:30:05 +00:00
template = self . response_template ( S3_NO_BUCKET_TAGGING )
return 404 , { } , template . render ( bucket_name = bucket_name )
2020-03-31 11:04:04 +00:00
template = self . response_template ( S3_OBJECT_TAGGING_RESPONSE )
2020-03-31 10:10:38 +00:00
return template . render ( tags = tags )
2018-01-03 04:47:57 +00:00
elif " logging " in querystring :
2020-06-06 12:15:50 +00:00
logging = self . backend . get_bucket_logging ( bucket_name )
if not logging :
2018-01-03 04:47:57 +00:00
template = self . response_template ( S3_NO_LOGGING_CONFIG )
return 200 , { } , template . render ( )
template = self . response_template ( S3_LOGGING_CONFIG )
2020-06-06 12:15:50 +00:00
return 200 , { } , template . render ( logging = logging )
2017-09-07 18:30:05 +00:00
elif " cors " in querystring :
2020-06-06 12:15:50 +00:00
cors = self . backend . get_bucket_cors ( bucket_name )
if len ( cors ) == 0 :
2017-09-07 18:30:05 +00:00
template = self . response_template ( S3_NO_CORS_CONFIG )
return 404 , { } , template . render ( bucket_name = bucket_name )
template = self . response_template ( S3_BUCKET_CORS_RESPONSE )
2020-06-06 12:15:50 +00:00
return template . render ( cors = cors )
2018-03-21 16:11:24 +00:00
elif " notification " in querystring :
2020-06-06 12:15:50 +00:00
notification_configuration = (
self . backend . get_bucket_notification_configuration ( bucket_name )
)
if not notification_configuration :
2018-03-21 16:11:24 +00:00
return 200 , { } , " "
template = self . response_template ( S3_GET_BUCKET_NOTIFICATION_CONFIG )
2020-06-06 12:15:50 +00:00
return template . render ( config = notification_configuration )
2019-05-25 10:19:00 +00:00
elif " accelerate " in querystring :
bucket = self . backend . get_bucket ( bucket_name )
if bucket . accelerate_configuration is None :
template = self . response_template ( S3_BUCKET_ACCELERATE_NOT_SET )
return 200 , { } , template . render ( )
template = self . response_template ( S3_BUCKET_ACCELERATE )
return template . render ( bucket = bucket )
2019-12-10 01:38:26 +00:00
elif " publicAccessBlock " in querystring :
2021-08-21 14:05:40 +00:00
public_block_config = self . backend . get_public_access_block ( bucket_name )
2019-12-10 01:38:26 +00:00
template = self . response_template ( S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION )
return template . render ( public_block_config = public_block_config )
2018-03-21 16:11:24 +00:00
2014-06-27 22:21:32 +00:00
elif " versions " in querystring :
delimiter = querystring . get ( " delimiter " , [ None ] ) [ 0 ]
key_marker = querystring . get ( " key-marker " , [ None ] ) [ 0 ]
2017-08-11 00:14:31 +00:00
prefix = querystring . get ( " prefix " , [ " " ] ) [ 0 ]
2014-06-27 22:21:32 +00:00
bucket = self . backend . get_bucket ( bucket_name )
2021-10-14 18:13:40 +00:00
(
versions ,
common_prefixes ,
delete_markers ,
) = self . backend . list_object_versions (
2022-03-11 21:28:45 +00:00
bucket_name , delimiter = delimiter , key_marker = key_marker , prefix = prefix
2014-06-27 22:21:32 +00:00
)
2021-10-14 18:13:40 +00:00
key_list = versions
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_BUCKET_GET_VERSIONS )
2021-01-15 18:28:28 +00:00
2017-02-17 03:51:04 +00:00
return (
200 ,
{ } ,
template . render (
2021-10-14 18:13:40 +00:00
common_prefixes = common_prefixes ,
2017-05-14 16:56:25 +00:00
key_list = key_list ,
2021-10-14 18:13:40 +00:00
delete_marker_list = delete_markers ,
2014-06-27 22:21:32 +00:00
bucket = bucket ,
2021-06-28 15:22:08 +00:00
prefix = prefix ,
2017-04-29 19:35:25 +00:00
max_keys = 1000 ,
2021-10-14 18:13:40 +00:00
delimiter = delimiter ,
key_marker = key_marker ,
2014-06-27 22:21:32 +00:00
is_truncated = " false " ,
2019-10-31 15:44:26 +00:00
) ,
2014-06-27 22:21:32 +00:00
)
2020-05-27 16:21:03 +00:00
elif " encryption " in querystring :
encryption = self . backend . get_bucket_encryption ( bucket_name )
if not encryption :
template = self . response_template ( S3_NO_ENCRYPTION )
return 404 , { } , template . render ( bucket_name = bucket_name )
template = self . response_template ( S3_ENCRYPTION_CONFIG )
return 200 , { } , template . render ( encryption = encryption )
2017-02-09 02:21:43 +00:00
elif querystring . get ( " list-type " , [ None ] ) [ 0 ] == " 2 " :
2017-02-17 03:51:04 +00:00
return 200 , { } , self . _handle_list_objects_v2 ( bucket_name , querystring )
2021-10-16 17:26:09 +00:00
elif " replication " in querystring :
replication = self . backend . get_bucket_replication ( bucket_name )
if not replication :
template = self . response_template ( S3_NO_REPLICATION )
return 404 , { } , template . render ( bucket_name = bucket_name )
template = self . response_template ( S3_REPLICATION_CONFIG )
return 200 , { } , template . render ( replication = replication )
2022-08-24 10:48:13 +00:00
elif " ownershipControls " in querystring :
2022-08-25 09:34:46 +00:00
ownership_rule = self . backend . get_bucket_ownership_controls ( bucket_name )
2022-08-24 10:48:13 +00:00
if not ownership_rule :
template = self . response_template ( S3_ERROR_BUCKET_ONWERSHIP_NOT_FOUND )
return 404 , { } , template . render ( bucket_name = bucket_name )
template = self . response_template ( S3_BUCKET_GET_OWNERSHIP_RULE )
return 200 , { } , template . render ( ownership_rule = ownership_rule )
2014-06-27 22:21:32 +00:00
2015-02-10 13:33:18 +00:00
bucket = self . backend . get_bucket ( bucket_name )
2014-07-09 01:20:29 +00:00
prefix = querystring . get ( " prefix " , [ None ] ) [ 0 ]
2021-07-26 06:40:39 +00:00
if prefix and isinstance ( prefix , bytes ) :
2017-03-16 02:53:27 +00:00
prefix = prefix . decode ( " utf-8 " )
2014-07-09 01:20:29 +00:00
delimiter = querystring . get ( " delimiter " , [ None ] ) [ 0 ]
2017-09-09 03:47:16 +00:00
max_keys = int ( querystring . get ( " max-keys " , [ 1000 ] ) [ 0 ] )
2017-09-09 04:25:02 +00:00
marker = querystring . get ( " marker " , [ None ] ) [ 0 ]
2021-08-28 16:13:52 +00:00
result_keys , result_folders = self . backend . list_objects (
2017-02-24 02:37:43 +00:00
bucket , prefix , delimiter
)
2022-06-01 11:03:40 +00:00
encoding_type = querystring . get ( " encoding-type " , [ None ] ) [ 0 ]
2017-09-09 04:25:02 +00:00
if marker :
result_keys = self . _get_results_from_token ( result_keys , marker )
2019-10-21 01:40:05 +00:00
result_keys , is_truncated , next_marker = self . _truncate_result (
result_keys , max_keys
)
2017-09-09 03:47:16 +00:00
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_BUCKET_GET_RESPONSE )
2017-02-17 03:51:04 +00:00
return (
200 ,
{ } ,
template . render (
2014-07-09 01:20:29 +00:00
bucket = bucket ,
prefix = prefix ,
delimiter = delimiter ,
result_keys = result_keys ,
2017-09-09 03:47:16 +00:00
result_folders = result_folders ,
is_truncated = is_truncated ,
2019-10-21 01:40:05 +00:00
next_marker = next_marker ,
2017-09-09 03:47:16 +00:00
max_keys = max_keys ,
2022-06-01 11:03:40 +00:00
encoding_type = encoding_type ,
2019-10-31 15:44:26 +00:00
) ,
2014-07-09 01:20:29 +00:00
)
2023-04-20 16:47:39 +00:00
def _set_action (
self , action_resource_type : str , method : str , querystring : Dict [ str , Any ]
) - > None :
2019-07-04 14:38:43 +00:00
action_set = False
for action_in_querystring , action in ACTION_MAP [ action_resource_type ] [
method
] . items ( ) :
if action_in_querystring in querystring :
self . data [ " Action " ] = action
action_set = True
if not action_set :
self . data [ " Action " ] = ACTION_MAP [ action_resource_type ] [ method ] [ " DEFAULT " ]
2023-04-20 16:47:39 +00:00
def _handle_list_objects_v2 (
self , bucket_name : str , querystring : Dict [ str , Any ]
) - > str :
2017-02-09 02:21:43 +00:00
template = self . response_template ( S3_BUCKET_GET_RESPONSE_V2 )
bucket = self . backend . get_bucket ( bucket_name )
2020-12-13 13:38:25 +00:00
continuation_token = querystring . get ( " continuation-token " , [ None ] ) [ 0 ]
if continuation_token is not None and continuation_token == " " :
raise InvalidContinuationToken ( )
2017-02-09 02:21:43 +00:00
prefix = querystring . get ( " prefix " , [ None ] ) [ 0 ]
2021-07-26 06:40:39 +00:00
if prefix and isinstance ( prefix , bytes ) :
2017-03-16 02:53:27 +00:00
prefix = prefix . decode ( " utf-8 " )
2017-02-09 02:21:43 +00:00
delimiter = querystring . get ( " delimiter " , [ None ] ) [ 0 ]
2021-08-28 16:13:52 +00:00
all_keys = self . backend . list_objects_v2 ( bucket , prefix , delimiter )
2017-02-09 02:21:43 +00:00
fetch_owner = querystring . get ( " fetch-owner " , [ False ] ) [ 0 ]
max_keys = int ( querystring . get ( " max-keys " , [ 1000 ] ) [ 0 ] )
start_after = querystring . get ( " start-after " , [ None ] ) [ 0 ]
2022-06-01 11:03:40 +00:00
encoding_type = querystring . get ( " encoding-type " , [ None ] ) [ 0 ]
2017-02-09 02:21:43 +00:00
if continuation_token or start_after :
limit = continuation_token or start_after
2019-09-17 02:42:10 +00:00
all_keys = self . _get_results_from_token ( all_keys , limit )
2017-02-09 02:21:43 +00:00
2019-09-16 03:35:36 +00:00
truncated_keys , is_truncated , next_continuation_token = self . _truncate_result (
all_keys , max_keys
)
result_keys , result_folders = self . _split_truncated_keys ( truncated_keys )
2017-02-09 02:21:43 +00:00
2019-08-20 07:01:37 +00:00
key_count = len ( result_keys ) + len ( result_folders )
2019-08-20 07:17:17 +00:00
2022-06-17 10:21:06 +00:00
if encoding_type == " url " :
prefix = urllib . parse . quote ( prefix ) if prefix else " "
result_folders = list (
map ( lambda folder : urllib . parse . quote ( folder ) , result_folders )
)
2017-02-09 02:21:43 +00:00
return template . render (
bucket = bucket ,
2017-03-16 03:13:09 +00:00
prefix = prefix or " " ,
2017-02-09 02:21:43 +00:00
delimiter = delimiter ,
2019-08-20 07:01:37 +00:00
key_count = key_count ,
2017-02-09 02:21:43 +00:00
result_keys = result_keys ,
result_folders = result_folders ,
fetch_owner = fetch_owner ,
max_keys = max_keys ,
is_truncated = is_truncated ,
next_continuation_token = next_continuation_token ,
start_after = None if continuation_token else start_after ,
2022-06-01 11:03:40 +00:00
encoding_type = encoding_type ,
2017-02-09 02:21:43 +00:00
)
2019-09-16 07:20:24 +00:00
@staticmethod
2023-04-20 16:47:39 +00:00
def _split_truncated_keys ( truncated_keys : Any ) - > Any : # type: ignore[misc]
2019-09-16 03:35:36 +00:00
result_keys = [ ]
result_folders = [ ]
for key in truncated_keys :
2019-09-16 07:20:24 +00:00
if isinstance ( key , FakeKey ) :
result_keys . append ( key )
2019-09-16 03:35:36 +00:00
else :
2019-09-16 07:20:24 +00:00
result_folders . append ( key )
2019-09-16 03:35:36 +00:00
return result_keys , result_folders
2023-04-20 16:47:39 +00:00
def _get_results_from_token ( self , result_keys : Any , token : Any ) - > Any :
2017-09-09 04:25:02 +00:00
continuation_index = 0
for key in result_keys :
2018-12-06 00:17:28 +00:00
if ( key . name if isinstance ( key , FakeKey ) else key ) > token :
2017-09-09 04:25:02 +00:00
break
continuation_index + = 1
return result_keys [ continuation_index : ]
2023-04-20 16:47:39 +00:00
def _truncate_result ( self , result_keys : Any , max_keys : int ) - > Any :
2021-10-14 09:49:50 +00:00
if max_keys == 0 :
result_keys = [ ]
is_truncated = True
next_continuation_token = None
elif len ( result_keys ) > max_keys :
2023-04-20 16:47:39 +00:00
is_truncated = " true " # type: ignore
2019-09-16 03:35:36 +00:00
result_keys = result_keys [ : max_keys ]
2019-09-16 07:20:24 +00:00
item = result_keys [ - 1 ]
2018-12-06 00:17:28 +00:00
next_continuation_token = item . name if isinstance ( item , FakeKey ) else item
2017-09-09 03:47:16 +00:00
else :
2023-04-20 16:47:39 +00:00
is_truncated = " false " # type: ignore
2017-09-09 03:47:16 +00:00
next_continuation_token = None
2019-09-16 03:35:36 +00:00
return result_keys , is_truncated , next_continuation_token
2017-09-09 03:47:16 +00:00
2023-04-20 16:47:39 +00:00
def _body_contains_location_constraint ( self , body : bytes ) - > bool :
2020-02-02 10:36:51 +00:00
if body :
try :
xmltodict . parse ( body ) [ " CreateBucketConfiguration " ] [ " LocationConstraint " ]
return True
except KeyError :
pass
return False
2023-04-20 16:47:39 +00:00
def _create_bucket_configuration_is_empty ( self , body : bytes ) - > bool :
2020-06-19 10:44:43 +00:00
if body :
try :
create_bucket_configuration = xmltodict . parse ( body ) [
" CreateBucketConfiguration "
]
del create_bucket_configuration [ " @xmlns " ]
if len ( create_bucket_configuration ) == 0 :
return True
except KeyError :
pass
return False
2023-04-20 16:47:39 +00:00
def _parse_pab_config ( self ) - > Dict [ str , Any ] :
2022-06-09 17:40:22 +00:00
parsed_xml = xmltodict . parse ( self . body )
2020-02-14 02:01:44 +00:00
parsed_xml [ " PublicAccessBlockConfiguration " ] . pop ( " @xmlns " , None )
return parsed_xml
2023-04-20 16:47:39 +00:00
def _bucket_response_put (
self ,
request : Any ,
region_name : str ,
bucket_name : str ,
querystring : Dict [ str , Any ] ,
) - > Union [ str , TYPE_RESPONSE ] :
2023-04-09 10:05:43 +00:00
if querystring and not request . headers . get ( " Content-Length " ) :
2017-05-01 19:13:12 +00:00
return 411 , { } , " Content-Length required "
2019-07-04 14:38:43 +00:00
self . _set_action ( " BUCKET " , " PUT " , querystring )
2019-07-26 19:05:04 +00:00
self . _authenticate_and_authorize_s3_action ( )
2019-07-04 14:38:43 +00:00
2021-08-17 05:16:59 +00:00
if " object-lock " in querystring :
2022-06-09 17:40:22 +00:00
config = self . _lock_config_from_body ( )
2021-08-17 05:16:59 +00:00
if not self . backend . get_bucket ( bucket_name ) . object_lock_enabled :
raise BucketMustHaveLockeEnabled
2021-08-21 14:05:40 +00:00
self . backend . put_object_lock_configuration (
2021-08-17 05:16:59 +00:00
bucket_name ,
2023-04-20 16:47:39 +00:00
config . get ( " enabled " ) , # type: ignore
2021-10-06 20:05:26 +00:00
config . get ( " mode " ) ,
config . get ( " days " ) ,
config . get ( " years " ) ,
2021-08-17 05:16:59 +00:00
)
2021-10-06 20:05:26 +00:00
return 200 , { } , " "
2021-08-17 05:16:59 +00:00
2014-06-27 17:34:00 +00:00
if " versioning " in querystring :
2022-06-09 17:40:22 +00:00
body = self . body . decode ( " utf-8 " )
ver = re . search ( r " <Status>([A-Za-z]+)</Status> " , body )
2014-06-27 17:34:00 +00:00
if ver :
2023-04-20 16:47:39 +00:00
self . backend . put_bucket_versioning ( bucket_name , ver . group ( 1 ) ) # type: ignore
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_BUCKET_VERSIONING )
2014-06-27 17:34:00 +00:00
return template . render ( bucket_versioning_status = ver . group ( 1 ) )
else :
2017-02-17 03:51:04 +00:00
return 404 , { } , " "
2015-06-03 03:11:23 +00:00
elif " lifecycle " in querystring :
2022-06-09 17:40:22 +00:00
rules = xmltodict . parse ( self . body ) [ " LifecycleConfiguration " ] [ " Rule " ]
2015-06-03 03:11:23 +00:00
if not isinstance ( rules , list ) :
# If there is only one rule, xmldict returns just the item
rules = [ rules ]
2021-08-21 14:05:40 +00:00
self . backend . put_bucket_lifecycle ( bucket_name , rules )
2015-06-03 03:11:23 +00:00
return " "
2015-07-23 21:33:52 +00:00
elif " policy " in querystring :
2022-06-09 17:40:22 +00:00
self . backend . put_bucket_policy ( bucket_name , self . body )
2015-07-23 21:33:52 +00:00
return " True "
2015-11-12 01:26:29 +00:00
elif " acl " in querystring :
2018-01-03 04:47:57 +00:00
# Headers are first. If not set, then look at the body (consistent with the documentation):
acls = self . _acl_from_headers ( request . headers )
if not acls :
2022-06-09 17:40:22 +00:00
acls = self . _acl_from_body ( )
2021-08-21 14:05:40 +00:00
self . backend . put_bucket_acl ( bucket_name , acls )
2015-11-12 01:26:29 +00:00
return " "
2017-09-07 18:30:05 +00:00
elif " tagging " in querystring :
2022-06-09 17:40:22 +00:00
tagging = self . _bucket_tagging_from_body ( )
2020-06-06 12:15:50 +00:00
self . backend . put_bucket_tagging ( bucket_name , tagging )
2017-09-07 18:30:05 +00:00
return " "
2015-11-04 23:55:41 +00:00
elif " website " in querystring :
2022-06-09 17:40:22 +00:00
self . backend . set_bucket_website_configuration ( bucket_name , self . body )
2015-11-04 23:55:41 +00:00
return " "
2017-09-07 18:30:05 +00:00
elif " cors " in querystring :
try :
2022-06-09 17:40:22 +00:00
self . backend . put_bucket_cors ( bucket_name , self . _cors_from_body ( ) )
2017-09-07 18:30:05 +00:00
return " "
except KeyError :
raise MalformedXML ( )
2018-01-03 04:47:57 +00:00
elif " logging " in querystring :
try :
2022-06-09 17:40:22 +00:00
self . backend . put_bucket_logging ( bucket_name , self . _logging_from_body ( ) )
2018-01-03 04:47:57 +00:00
return " "
except KeyError :
raise MalformedXML ( )
2018-03-21 16:11:24 +00:00
elif " notification " in querystring :
try :
self . backend . put_bucket_notification_configuration (
2022-06-09 17:40:22 +00:00
bucket_name , self . _notification_config_from_body ( )
2019-10-31 15:44:26 +00:00
)
2018-03-21 16:11:24 +00:00
return " "
except KeyError :
raise MalformedXML ( )
except Exception as e :
raise e
2019-05-25 10:19:00 +00:00
elif " accelerate " in querystring :
try :
2022-06-09 17:40:22 +00:00
accelerate_status = self . _accelerate_config_from_body ( )
2019-05-25 10:19:00 +00:00
self . backend . put_bucket_accelerate_configuration (
bucket_name , accelerate_status
)
return " "
except KeyError :
raise MalformedXML ( )
except Exception as e :
raise e
2018-01-03 04:47:57 +00:00
2019-12-10 01:38:26 +00:00
elif " publicAccessBlock " in querystring :
2022-06-09 17:40:22 +00:00
pab_config = self . _parse_pab_config ( )
2019-12-10 01:38:26 +00:00
self . backend . put_bucket_public_access_block (
2020-02-14 02:01:44 +00:00
bucket_name , pab_config [ " PublicAccessBlockConfiguration " ]
2019-12-10 01:38:26 +00:00
)
return " "
2020-05-27 16:21:03 +00:00
elif " encryption " in querystring :
try :
self . backend . put_bucket_encryption (
2022-06-09 17:40:22 +00:00
bucket_name , self . _encryption_config_from_body ( )
2020-05-27 16:21:03 +00:00
)
return " "
except KeyError :
raise MalformedXML ( )
except Exception as e :
raise e
2021-10-16 17:26:09 +00:00
elif " replication " in querystring :
bucket = self . backend . get_bucket ( bucket_name )
if not bucket . is_versioned :
template = self . response_template ( S3_NO_VERSIONING_ENABLED )
return 400 , { } , template . render ( bucket_name = bucket_name )
2022-06-09 17:40:22 +00:00
replication_config = self . _replication_config_from_xml ( self . body )
2021-10-16 17:26:09 +00:00
self . backend . put_bucket_replication ( bucket_name , replication_config )
return " "
2022-08-24 10:48:13 +00:00
elif " ownershipControls " in querystring :
ownership_rule = self . _ownership_rule_from_body ( )
2022-08-25 09:34:46 +00:00
self . backend . put_bucket_ownership_controls (
2022-08-24 10:48:13 +00:00
bucket_name , ownership = ownership_rule
)
return " "
2014-06-27 17:34:00 +00:00
else :
2020-02-02 10:36:51 +00:00
# us-east-1, the default AWS region behaves a bit differently
# - you should not use it as a location constraint --> it fails
# - querying the location constraint returns None
# - LocationConstraint has to be specified if outside us-east-1
if (
region_name != DEFAULT_REGION_NAME
2022-06-09 17:40:22 +00:00
and not self . _body_contains_location_constraint ( self . body )
2020-02-02 10:36:51 +00:00
) :
raise IllegalLocationConstraintException ( )
2022-06-09 17:40:22 +00:00
if self . body :
if self . _create_bucket_configuration_is_empty ( self . body ) :
2020-06-19 10:44:43 +00:00
raise MalformedXML ( )
2017-06-03 23:29:59 +00:00
try :
2022-06-09 17:40:22 +00:00
forced_region = xmltodict . parse ( self . body ) [
" CreateBucketConfiguration "
] [ " LocationConstraint " ]
2018-11-20 17:52:50 +00:00
if forced_region == DEFAULT_REGION_NAME :
raise S3ClientError (
" InvalidLocationConstraint " ,
" The specified location-constraint is not valid " ,
)
else :
region_name = forced_region
2017-06-03 23:29:59 +00:00
except KeyError :
pass
2014-06-27 17:34:00 +00:00
try :
2017-02-24 02:37:43 +00:00
new_bucket = self . backend . create_bucket ( bucket_name , region_name )
2014-06-27 17:34:00 +00:00
except BucketAlreadyExists :
2021-09-01 15:30:01 +00:00
new_bucket = self . backend . get_bucket ( bucket_name )
if (
new_bucket . region_name == DEFAULT_REGION_NAME
and region_name == DEFAULT_REGION_NAME
) :
# us-east-1 has different behavior - creating a bucket there is an idempotent operation
pass
2014-12-11 01:20:43 +00:00
else :
2021-09-01 15:30:01 +00:00
template = self . response_template ( S3_DUPLICATE_BUCKET_ERROR )
return 409 , { } , template . render ( bucket_name = bucket_name )
2017-09-16 13:08:27 +00:00
if " x-amz-acl " in request . headers :
# TODO: Support the XML-based ACL format
2021-08-21 14:05:40 +00:00
self . backend . put_bucket_acl (
2017-09-16 13:08:27 +00:00
bucket_name , self . _acl_from_headers ( request . headers )
2019-10-31 15:44:26 +00:00
)
2017-09-16 13:08:27 +00:00
2021-10-06 20:05:26 +00:00
if (
request . headers . get ( " x-amz-bucket-object-lock-enabled " , " " ) . lower ( )
== " true "
) :
2021-08-17 05:16:59 +00:00
new_bucket . object_lock_enabled = True
new_bucket . versioning_status = " Enabled "
2022-08-24 10:48:13 +00:00
ownership_rule = request . headers . get ( " x-amz-object-ownership " )
if ownership_rule :
new_bucket . ownership_rule = ownership_rule
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_BUCKET_CREATE_RESPONSE )
2017-02-17 03:51:04 +00:00
return 200 , { } , template . render ( bucket = new_bucket )
2014-03-30 15:50:36 +00:00
2023-04-20 16:47:39 +00:00
def _bucket_response_delete (
self , bucket_name : str , querystring : Dict [ str , Any ]
) - > TYPE_RESPONSE :
2019-07-04 14:38:43 +00:00
self . _set_action ( " BUCKET " , " DELETE " , querystring )
2019-07-26 19:05:04 +00:00
self . _authenticate_and_authorize_s3_action ( )
2019-07-04 14:38:43 +00:00
2015-08-08 02:43:24 +00:00
if " policy " in querystring :
2022-03-11 21:28:45 +00:00
self . backend . delete_bucket_policy ( bucket_name )
2017-02-17 03:51:04 +00:00
return 204 , { } , " "
2017-09-07 18:30:05 +00:00
elif " tagging " in querystring :
self . backend . delete_bucket_tagging ( bucket_name )
return 204 , { } , " "
2021-07-26 14:21:17 +00:00
elif " website " in querystring :
self . backend . delete_bucket_website ( bucket_name )
return 204 , { } , " "
2017-09-07 18:30:05 +00:00
elif " cors " in querystring :
self . backend . delete_bucket_cors ( bucket_name )
return 204 , { } , " "
2015-08-08 02:43:24 +00:00
elif " lifecycle " in querystring :
2021-08-21 14:05:40 +00:00
self . backend . delete_bucket_lifecycle ( bucket_name )
2017-02-17 03:51:04 +00:00
return 204 , { } , " "
2019-12-10 01:38:26 +00:00
elif " publicAccessBlock " in querystring :
2021-08-21 14:05:40 +00:00
self . backend . delete_public_access_block ( bucket_name )
2019-12-10 01:38:26 +00:00
return 204 , { } , " "
2020-05-27 16:21:03 +00:00
elif " encryption " in querystring :
2021-08-21 14:05:40 +00:00
self . backend . delete_bucket_encryption ( bucket_name )
2020-05-27 16:21:03 +00:00
return 204 , { } , " "
2021-10-16 17:26:09 +00:00
elif " replication " in querystring :
self . backend . delete_bucket_replication ( bucket_name )
return 204 , { } , " "
2022-08-24 10:48:13 +00:00
elif " ownershipControls " in querystring :
2022-08-25 09:34:46 +00:00
self . backend . delete_bucket_ownership_controls ( bucket_name )
2022-08-24 10:48:13 +00:00
return 204 , { } , " "
2015-06-03 03:11:23 +00:00
2015-02-10 13:33:18 +00:00
removed_bucket = self . backend . delete_bucket ( bucket_name )
2014-07-09 01:20:29 +00:00
if removed_bucket :
2014-03-30 15:50:36 +00:00
# Bucket exists
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_DELETE_BUCKET_SUCCESS )
2017-02-17 03:51:04 +00:00
return 204 , { } , template . render ( bucket = removed_bucket )
2013-02-19 02:22:03 +00:00
else :
2014-03-30 15:50:36 +00:00
# Tried to delete a bucket that still has keys
2017-02-24 02:37:43 +00:00
template = self . response_template ( S3_DELETE_BUCKET_WITH_ITEMS_ERROR )
2017-02-17 03:51:04 +00:00
return 409 , { } , template . render ( bucket = removed_bucket )
2014-03-30 15:50:36 +00:00
2023-04-20 16:47:39 +00:00
def _bucket_response_post ( self , request : Any , bucket_name : str ) - > TYPE_RESPONSE :
2019-04-16 00:57:42 +00:00
response_headers = { }
2017-05-01 19:13:12 +00:00
if not request . headers . get ( " Content-Length " ) :
return 411 , { } , " Content-Length required "
2017-11-06 21:39:08 +00:00
2022-06-09 17:40:22 +00:00
self . path = self . _get_path ( request )
2017-11-06 21:39:08 +00:00
2022-10-29 15:02:46 +00:00
if self . is_delete_keys ( ) :
2019-07-04 14:38:43 +00:00
self . data [ " Action " ] = " DeleteObject "
2022-05-12 13:59:47 +00:00
try :
self . _authenticate_and_authorize_s3_action ( )
2022-06-09 17:40:22 +00:00
return self . _bucket_response_delete_keys ( bucket_name )
2022-05-12 13:59:47 +00:00
except BucketAccessDeniedError :
return self . _bucket_response_delete_keys (
2022-06-09 17:40:22 +00:00
bucket_name , authenticated = False
2022-05-12 13:59:47 +00:00
)
2014-05-06 21:21:33 +00:00
2019-07-04 14:38:43 +00:00
self . data [ " Action " ] = " PutObject "
2019-07-26 19:05:04 +00:00
self . _authenticate_and_authorize_s3_action ( )
2014-05-06 21:21:33 +00:00
2014-11-15 14:35:52 +00:00
# POST to bucket-url should create file from form
2022-01-18 15:18:57 +00:00
form = request . form
2014-03-30 15:50:36 +00:00
key = form [ " key " ]
if " file " in form :
f = form [ " file " ]
else :
2021-02-10 09:06:03 +00:00
fobj = request . files [ " file " ]
f = fobj . stream . read ( )
key = key . replace ( " $ {filename} " , os . path . basename ( fobj . filename ) )
2014-03-30 15:50:36 +00:00
2020-04-22 00:51:48 +00:00
if " success_action_redirect " in form :
2021-02-10 09:06:03 +00:00
redirect = form [ " success_action_redirect " ]
parts = urlparse ( redirect )
2023-04-20 16:47:39 +00:00
queryargs : Dict [ str , Any ] = parse_qs ( parts . query )
2021-02-10 09:06:03 +00:00
queryargs [ " key " ] = key
queryargs [ " bucket " ] = bucket_name
redirect_queryargs = urlencode ( queryargs , doseq = True )
newparts = (
parts . scheme ,
parts . netloc ,
parts . path ,
parts . params ,
redirect_queryargs ,
parts . fragment ,
)
fixed_redirect = urlunparse ( newparts )
response_headers [ " Location " ] = fixed_redirect
2019-04-16 00:57:42 +00:00
2020-04-22 00:51:48 +00:00
if " success_action_status " in form :
status_code = form [ " success_action_status " ]
elif " success_action_redirect " in form :
2019-05-29 20:22:29 +00:00
status_code = 303
2019-04-16 00:57:42 +00:00
else :
status_code = 204
2021-08-21 14:05:40 +00:00
new_key = self . backend . put_object ( bucket_name , key , f )
2014-03-30 15:50:36 +00:00
2020-09-02 17:35:53 +00:00
if form . get ( " acl " ) :
acl = get_canned_acl ( form . get ( " acl " ) )
new_key . set_acl ( acl )
2014-11-15 14:35:52 +00:00
# Metadata
2014-12-07 17:43:14 +00:00
metadata = metadata_from_headers ( form )
new_key . set_metadata ( metadata )
2019-04-16 00:57:42 +00:00
return status_code , response_headers , " "
2013-10-28 20:43:25 +00:00
2019-07-04 14:38:43 +00:00
@staticmethod
2023-04-20 16:47:39 +00:00
def _get_path ( request : Any ) - > str : # type: ignore[misc]
2022-01-18 15:18:57 +00:00
return (
request . full_path
if hasattr ( request , " full_path " )
else path_url ( request . url )
)
2019-07-04 14:38:43 +00:00
2023-04-20 16:47:39 +00:00
def _bucket_response_delete_keys (
self , bucket_name : str , authenticated : bool = True
) - > TYPE_RESPONSE :
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_DELETE_KEYS_RESPONSE )
2022-06-09 17:40:22 +00:00
body_dict = xmltodict . parse ( self . body , strip_whitespace = False )
2014-05-06 21:21:33 +00:00
2020-04-27 09:42:27 +00:00
objects = body_dict [ " Delete " ] . get ( " Object " , [ ] )
if not isinstance ( objects , list ) :
# We expect a list of objects, but when there is a single <Object> node xmltodict does not
# return a list.
objects = [ objects ]
if len ( objects ) == 0 :
raise MalformedXML ( )
2014-05-06 21:21:33 +00:00
2022-05-12 13:59:47 +00:00
if authenticated :
deleted_objects = self . backend . delete_objects ( bucket_name , objects )
errors = [ ]
else :
deleted_objects = [ ]
# [(key_name, errorcode, 'error message'), ..]
errors = [ ( o [ " Key " ] , " AccessDenied " , " Access Denied " ) for o in objects ]
2014-05-06 21:21:33 +00:00
2017-02-17 03:51:04 +00:00
return (
200 ,
{ } ,
2022-05-12 13:59:47 +00:00
template . render ( deleted = deleted_objects , delete_errors = errors ) ,
2017-02-17 03:51:04 +00:00
)
2014-05-06 21:21:33 +00:00
2023-04-20 16:47:39 +00:00
def _handle_range_header (
self , request : Any , response_headers : Dict [ str , Any ] , response_content : Any
) - > TYPE_RESPONSE :
2015-02-10 17:14:47 +00:00
length = len ( response_content )
2015-02-10 17:43:24 +00:00
last = length - 1
2015-02-10 17:14:47 +00:00
_ , rspec = request . headers . get ( " range " ) . split ( " = " )
if " , " in rspec :
raise NotImplementedError ( " Multiple range specifiers not supported " )
2017-02-24 02:37:43 +00:00
2023-04-20 16:47:39 +00:00
def toint ( i : Any ) - > Optional [ int ] :
2017-02-24 02:37:43 +00:00
return int ( i ) if i else None
2018-01-03 04:47:57 +00:00
2015-02-10 17:14:47 +00:00
begin , end = map ( toint , rspec . split ( " - " ) )
if begin is not None : # byte range
2015-06-30 01:25:22 +00:00
end = last if end is None else min ( end , last )
2015-02-10 17:14:47 +00:00
elif end is not None : # suffix byte range
2015-06-30 07:04:30 +00:00
begin = length - min ( end , length )
2015-02-10 17:43:24 +00:00
end = last
2015-02-10 17:14:47 +00:00
else :
2017-02-17 03:51:04 +00:00
return 400 , response_headers , " "
2015-06-30 05:26:42 +00:00
if begin < 0 or end > last or begin > min ( end , last ) :
2020-10-28 14:22:18 +00:00
raise InvalidRange (
actual_size = str ( length ) , range_requested = request . headers . get ( " range " )
)
2022-11-21 19:21:34 +00:00
response_headers [ " content-range " ] = f " bytes { begin } - { end } / { length } "
2020-10-28 14:22:18 +00:00
content = response_content [ begin : end + 1 ]
response_headers [ " content-length " ] = len ( content )
return 206 , response_headers , content
2015-02-10 17:14:47 +00:00
2023-04-20 16:47:39 +00:00
def _handle_v4_chunk_signatures ( self , body : bytes , content_length : int ) - > bytes :
2021-08-19 14:06:43 +00:00
body_io = io . BytesIO ( body )
new_body = bytearray ( content_length )
pos = 0
line = body_io . readline ( )
while line :
# https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html#sigv4-chunked-body-definition
# str(hex(chunk-size)) + ";chunk-signature=" + signature + \r\n + chunk-data + \r\n
chunk_size = int ( line [ : line . find ( b " ; " ) ] . decode ( " utf8 " ) , 16 )
new_body [ pos : pos + chunk_size ] = body_io . read ( chunk_size )
pos = pos + chunk_size
body_io . read ( 2 ) # skip trailing \r\n
line = body_io . readline ( )
return bytes ( new_body )
2023-04-20 16:47:39 +00:00
def _handle_encoded_body ( self , body : bytes , content_length : int ) - > bytes :
2022-09-28 09:33:27 +00:00
body_io = io . BytesIO ( body )
# first line should equal '{content_length}\r\n
body_io . readline ( )
# Body contains actual data next
return body_io . read ( content_length )
# last line should equal
# amz-checksum-sha256:<..>\r\n
2021-08-28 07:32:14 +00:00
@amzn_request_id
2023-04-20 16:47:39 +00:00
def key_response ( self , request : Any , full_url : str , headers : Dict [ str , Any ] ) - > TYPE_RESPONSE : # type: ignore[misc]
2020-02-14 02:01:44 +00:00
# Key and Control are lumped in because splitting out the regex is too much of a pain :/
2022-06-09 17:40:22 +00:00
self . setup_class ( request , full_url , headers , use_raw_body = True )
2023-01-16 23:36:08 +00:00
bucket_name = self . parse_bucket_name_from_url ( request , full_url )
self . backend . log_incoming_request ( request , bucket_name )
2023-04-20 16:47:39 +00:00
response_headers : Dict [ str , Any ] = { }
2020-02-14 02:01:44 +00:00
2014-07-09 01:20:29 +00:00
try :
2022-01-18 20:10:22 +00:00
response = self . _key_response ( request , full_url , self . headers )
2015-02-10 13:33:18 +00:00
except S3ClientError as s3error :
2017-02-17 03:51:04 +00:00
response = s3error . code , { } , s3error . description
2014-07-09 01:20:29 +00:00
2021-07-26 06:40:39 +00:00
if isinstance ( response , str ) :
2015-02-10 17:14:47 +00:00
status_code = 200
response_content = response
2013-10-28 20:43:25 +00:00
else :
2017-02-17 03:51:04 +00:00
status_code , response_headers , response_content = response
2015-02-10 17:14:47 +00:00
2021-01-13 10:00:18 +00:00
if (
status_code == 200
and " range " in request . headers
and request . headers [ " range " ] != " "
) :
2020-10-28 14:22:18 +00:00
try :
return self . _handle_range_header (
request , response_headers , response_content
)
except S3ClientError as s3error :
return s3error . code , { } , s3error . description
2017-02-17 03:51:04 +00:00
return status_code , response_headers , response_content
2013-03-26 15:50:18 +00:00
2023-04-20 16:47:39 +00:00
def _key_response (
self , request : Any , full_url : str , headers : Dict [ str , Any ]
) - > TYPE_RESPONSE :
2013-10-28 20:43:25 +00:00
parsed_url = urlparse ( full_url )
2015-10-07 07:04:22 +00:00
query = parse_qs ( parsed_url . query , keep_blank_values = True )
2013-10-28 20:43:25 +00:00
method = request . method
2013-03-26 15:50:18 +00:00
2015-11-27 18:49:44 +00:00
key_name = self . parse_key_name ( request , parsed_url . path )
bucket_name = self . parse_bucket_name_from_url ( request , full_url )
2013-10-28 20:43:25 +00:00
2023-01-31 11:33:57 +00:00
# SDK requests tend to have Authorization set automatically
# If users make an HTTP-request, such as `requests.get("https://bucket-name.s3.amazonaws.com/file-name")`,
# The authorization-header may not be set
authorized_request = " Authorization " in request . headers
if hasattr ( request , " url " ) :
signed_url = " Signature= " in request . url
elif hasattr ( request , " requestline " ) :
signed_url = " Signature= " in request . path
try :
2020-06-06 12:15:50 +00:00
key = self . backend . get_object ( bucket_name , key_name )
2023-01-31 11:33:57 +00:00
bucket = self . backend . get_bucket ( bucket_name )
except S3ClientError :
key = bucket = None
if key :
resource = f " arn:aws:s3::: { bucket_name } / { key_name } "
# Authorization Workflow
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-auth-workflow-object-operation.html
# A bucket can deny all actions, regardless of who makes the request
from moto . iam . access_control import PermissionResult
action = f " s3: { method . upper ( ) [ 0 ] } { method . lower ( ) [ 1 : ] } Object "
2023-04-20 16:47:39 +00:00
bucket_permissions = bucket . get_permission ( action , resource ) # type: ignore
2023-01-31 11:33:57 +00:00
if bucket_permissions == PermissionResult . DENIED :
return 403 , { } , " "
# If the request is not authorized, and not signed,
# that means that the action should be allowed for anonymous users
if not authorized_request and not signed_url :
# We already know that the bucket permissions do not explicitly deny this
# So bucket permissions are either not set, or do not explicitly allow
# Next check is to see if the ACL of the individual key allows this action
if bucket_permissions != PermissionResult . PERMITTED and (
key . acl and not key . acl . public_read
) :
2017-09-22 17:42:13 +00:00
return 403 , { } , " "
2023-01-31 11:33:57 +00:00
elif signed_url and not authorized_request :
# coming in from requests.get(s3.generate_presigned_url())
if self . _invalid_headers ( request . url , dict ( request . headers ) ) :
return 403 , { } , S3_INVALID_PRESIGNED_PARAMETERS
2017-09-16 13:08:27 +00:00
2013-10-28 20:43:25 +00:00
if hasattr ( request , " body " ) :
# Boto
body = request . body
2017-02-16 03:35:45 +00:00
if hasattr ( body , " read " ) :
body = body . read ( )
2013-10-28 20:43:25 +00:00
else :
# Flask server
body = request . data
2021-08-28 05:10:16 +00:00
if not body :
# when the data is being passed as a file
if request . files :
for _ , value in request . files . items ( ) :
body = value . stream . read ( )
elif hasattr ( request , " form " ) :
# Body comes through as part of the form, if no content-type is set on the PUT-request
# form = ImmutableMultiDict([('some data 123 321', '')])
form = request . form
for k , _ in form . items ( ) :
body = k
2020-09-02 06:10:56 +00:00
2017-02-16 03:35:45 +00:00
if body is None :
body = b " "
2019-10-31 15:44:26 +00:00
2021-08-19 14:06:43 +00:00
if (
request . headers . get ( " x-amz-content-sha256 " , None )
== " STREAMING-AWS4-HMAC-SHA256-PAYLOAD "
) :
body = self . _handle_v4_chunk_signatures (
body , int ( request . headers [ " x-amz-decoded-content-length " ] )
)
2013-10-28 20:43:25 +00:00
if method == " GET " :
2019-07-03 15:35:56 +00:00
return self . _key_response_get (
bucket_name , query , key_name , headers = request . headers
)
2014-03-30 15:50:36 +00:00
elif method == " PUT " :
2022-03-11 21:28:45 +00:00
return self . _key_response_put ( request , body , bucket_name , query , key_name )
2013-10-28 20:43:25 +00:00
elif method == " HEAD " :
2017-05-23 18:29:01 +00:00
return self . _key_response_head (
bucket_name , query , key_name , headers = request . headers
)
2013-10-28 20:43:25 +00:00
elif method == " DELETE " :
2021-10-06 20:05:26 +00:00
return self . _key_response_delete ( headers , bucket_name , query , key_name )
2013-11-14 15:05:46 +00:00
elif method == " POST " :
2019-07-16 14:27:50 +00:00
return self . _key_response_post ( request , body , bucket_name , query , key_name )
2021-11-05 13:19:06 +00:00
elif method == " OPTIONS " :
# OPTIONS response doesn't depend on the key_name: always return 200 with CORS headers
2023-03-03 22:40:55 +00:00
return self . _response_options ( request . headers , bucket_name )
2014-03-30 15:50:36 +00:00
else :
2017-02-24 02:37:43 +00:00
raise NotImplementedError (
2022-11-21 19:21:34 +00:00
f " Method { method } has not been implemented in the S3 backend yet "
2019-10-31 15:44:26 +00:00
)
2014-03-30 15:50:36 +00:00
2023-04-20 16:47:39 +00:00
def _key_response_get (
self ,
bucket_name : str ,
query : Dict [ str , Any ] ,
key_name : str ,
headers : Dict [ str , Any ] ,
) - > TYPE_RESPONSE :
2019-07-04 14:38:43 +00:00
self . _set_action ( " KEY " , " GET " , query )
2019-07-26 19:05:04 +00:00
self . _authenticate_and_authorize_s3_action ( )
2019-07-04 14:38:43 +00:00
2023-04-20 16:47:39 +00:00
response_headers : Dict [ str , Any ] = { }
2015-10-07 07:04:22 +00:00
if query . get ( " uploadId " ) :
2014-03-30 15:50:36 +00:00
upload_id = query [ " uploadId " ] [ 0 ]
2021-08-28 06:38:16 +00:00
# 0 <= PartNumberMarker <= 2,147,483,647
part_number_marker = int ( query . get ( " part-number-marker " , [ 0 ] ) [ 0 ] )
2021-11-23 19:47:48 +00:00
if part_number_marker > 2147483647 :
raise NotAnIntegerException (
name = " part-number-marker " , value = part_number_marker
)
2021-08-28 06:38:16 +00:00
if not ( 0 < = part_number_marker < = 2147483647 ) :
raise InvalidMaxPartArgument ( " part-number-marker " , 0 , 2147483647 )
# 0 <= MaxParts <= 2,147,483,647 (default is 1,000)
max_parts = int ( query . get ( " max-parts " , [ 1000 ] ) [ 0 ] )
2021-11-23 19:47:48 +00:00
if max_parts > 2147483647 :
raise NotAnIntegerException ( name = " max-parts " , value = max_parts )
2021-08-28 06:38:16 +00:00
if not ( 0 < = max_parts < = 2147483647 ) :
raise InvalidMaxPartArgument ( " max-parts " , 0 , 2147483647 )
2021-08-28 16:13:52 +00:00
parts = self . backend . list_parts (
2021-08-28 06:38:16 +00:00
bucket_name ,
upload_id ,
part_number_marker = part_number_marker ,
max_parts = max_parts ,
)
2021-11-23 19:47:48 +00:00
next_part_number_marker = parts [ - 1 ] . name if parts else 0
2021-12-27 19:23:26 +00:00
is_truncated = len ( parts ) != 0 and self . backend . is_truncated (
2023-04-20 16:47:39 +00:00
bucket_name , upload_id , next_part_number_marker # type: ignore
2021-08-28 06:38:16 +00:00
)
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_MULTIPART_LIST_RESPONSE )
2017-02-17 03:51:04 +00:00
return (
200 ,
response_headers ,
template . render (
2014-03-30 15:50:36 +00:00
bucket_name = bucket_name ,
key_name = key_name ,
upload_id = upload_id ,
2021-08-28 06:38:16 +00:00
is_truncated = str ( is_truncated ) . lower ( ) ,
max_parts = max_parts ,
next_part_number_marker = next_part_number_marker ,
2014-03-30 15:50:36 +00:00
parts = parts ,
2021-08-28 06:38:16 +00:00
part_number_marker = part_number_marker ,
2019-10-31 15:44:26 +00:00
) ,
2014-03-30 15:50:36 +00:00
)
2014-06-27 22:21:32 +00:00
version_id = query . get ( " versionId " , [ None ] ) [ 0 ]
2019-07-03 15:35:56 +00:00
if_modified_since = headers . get ( " If-Modified-Since " , None )
2020-09-11 09:17:39 +00:00
if_match = headers . get ( " If-Match " , None )
if_none_match = headers . get ( " If-None-Match " , None )
if_unmodified_since = headers . get ( " If-Unmodified-Since " , None )
2020-06-06 12:15:50 +00:00
key = self . backend . get_object ( bucket_name , key_name , version_id = version_id )
2021-01-13 16:06:09 +00:00
if key is None and version_id is None :
2021-11-14 17:16:58 +00:00
raise MissingKey ( key = key_name )
2021-01-13 16:06:09 +00:00
elif key is None :
2021-04-30 11:36:08 +00:00
raise MissingVersion ( )
2020-09-11 09:17:39 +00:00
2022-01-26 23:24:51 +00:00
if key . version_id :
response_headers [ " x-amz-version-id " ] = key . version_id
2023-02-27 15:44:30 +00:00
if key . storage_class in ARCHIVE_STORAGE_CLASSES :
2023-03-09 22:00:17 +00:00
if ' ongoing-request= " false " ' not in key . response_dict . get (
" x-amz-restore " , " "
) :
raise InvalidObjectState ( storage_class = key . storage_class )
2020-09-11 09:17:39 +00:00
if if_unmodified_since :
if_unmodified_since = str_to_rfc_1123_datetime ( if_unmodified_since )
2022-11-16 21:12:48 +00:00
if key . last_modified . replace ( microsecond = 0 ) > if_unmodified_since :
2020-09-11 09:17:39 +00:00
raise PreconditionFailed ( " If-Unmodified-Since " )
2022-11-21 19:21:34 +00:00
if if_match and key . etag not in [ if_match , f ' " { if_match } " ' ] :
2020-09-11 09:17:39 +00:00
raise PreconditionFailed ( " If-Match " )
2019-07-03 15:35:56 +00:00
if if_modified_since :
if_modified_since = str_to_rfc_1123_datetime ( if_modified_since )
2022-11-16 21:12:48 +00:00
if key . last_modified . replace ( microsecond = 0 ) < = if_modified_since :
2020-09-11 09:17:39 +00:00
return 304 , response_headers , " Not Modified "
2022-11-29 09:42:08 +00:00
if if_none_match and key . etag in [ if_none_match , f ' " { if_none_match } " ' ] :
2019-07-03 15:35:56 +00:00
return 304 , response_headers , " Not Modified "
2020-09-11 09:17:39 +00:00
2015-10-07 07:04:22 +00:00
if " acl " in query :
2022-06-09 17:40:22 +00:00
acl = self . backend . get_object_acl ( key )
2015-10-07 07:04:22 +00:00
template = self . response_template ( S3_OBJECT_ACL_RESPONSE )
2021-08-21 14:05:40 +00:00
return 200 , response_headers , template . render ( acl = acl )
2017-07-16 02:36:12 +00:00
if " tagging " in query :
2021-08-21 14:05:40 +00:00
tags = self . backend . get_object_tagging ( key ) [ " Tags " ]
2017-07-16 02:36:12 +00:00
template = self . response_template ( S3_OBJECT_TAGGING_RESPONSE )
2020-03-31 11:04:04 +00:00
return 200 , response_headers , template . render ( tags = tags )
2021-10-06 20:05:26 +00:00
if " legal-hold " in query :
legal_hold = self . backend . get_object_legal_hold ( key )
template = self . response_template ( S3_OBJECT_LEGAL_HOLD )
return 200 , response_headers , template . render ( legal_hold = legal_hold )
2023-03-16 11:56:20 +00:00
if " attributes " in query :
attributes_to_get = headers . get ( " x-amz-object-attributes " , " " ) . split ( " , " )
response_keys = self . backend . get_object_attributes ( key , attributes_to_get )
2023-04-20 16:47:39 +00:00
if key . version_id == " null " : # type: ignore
2023-03-16 11:56:20 +00:00
response_headers . pop ( " x-amz-version-id " )
response_headers [ " Last-Modified " ] = key . last_modified_ISO8601
template = self . response_template ( S3_OBJECT_ATTRIBUTES_RESPONSE )
return 200 , response_headers , template . render ( * * response_keys )
2015-10-07 07:04:22 +00:00
2017-02-17 03:51:04 +00:00
response_headers . update ( key . metadata )
response_headers . update ( key . response_dict )
2023-03-24 11:55:56 +00:00
response_headers . update ( { " AcceptRanges " : " bytes " } )
2017-02-17 03:51:04 +00:00
return 200 , response_headers , key . value
2014-03-30 15:50:36 +00:00
2023-04-20 16:47:39 +00:00
def _key_response_put (
self ,
request : Any ,
body : bytes ,
bucket_name : str ,
query : Dict [ str , Any ] ,
key_name : str ,
) - > TYPE_RESPONSE :
2019-07-04 14:38:43 +00:00
self . _set_action ( " KEY " , " PUT " , query )
2019-07-26 19:05:04 +00:00
self . _authenticate_and_authorize_s3_action ( )
2019-07-04 14:38:43 +00:00
2023-04-20 16:47:39 +00:00
response_headers : Dict [ str , Any ] = { }
2015-10-07 07:04:22 +00:00
if query . get ( " uploadId " ) and query . get ( " partNumber " ) :
2014-03-30 15:50:36 +00:00
upload_id = query [ " uploadId " ] [ 0 ]
part_number = int ( query [ " partNumber " ] [ 0 ] )
if " x-amz-copy-source " in request . headers :
2021-11-01 22:17:06 +00:00
copy_source = request . headers . get ( " x-amz-copy-source " )
if isinstance ( copy_source , bytes ) :
copy_source = copy_source . decode ( " utf-8 " )
copy_source_parsed = urlparse ( copy_source )
src_bucket , src_key = copy_source_parsed . path . lstrip ( " / " ) . split ( " / " , 1 )
src_version_id = parse_qs ( copy_source_parsed . query ) . get (
2023-04-20 16:47:39 +00:00
" versionId " , [ None ] # type: ignore
2021-11-01 22:17:06 +00:00
) [ 0 ]
2017-02-24 02:37:43 +00:00
src_range = request . headers . get ( " x-amz-copy-source-range " , " " ) . split (
" bytes= "
) [ - 1 ]
2016-05-06 02:52:12 +00:00
try :
start_byte , end_byte = src_range . split ( " - " )
start_byte , end_byte = int ( start_byte ) , int ( end_byte )
except ValueError :
start_byte , end_byte = None , None
2020-06-06 12:15:50 +00:00
if self . backend . get_object (
src_bucket , src_key , version_id = src_version_id
) :
2019-05-25 19:19:33 +00:00
key = self . backend . copy_part (
bucket_name ,
upload_id ,
part_number ,
src_bucket ,
src_key ,
src_version_id ,
start_byte ,
end_byte ,
)
else :
return 404 , response_headers , " "
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_MULTIPART_UPLOAD_RESPONSE )
2014-03-30 15:50:36 +00:00
response = template . render ( part = key )
else :
2021-11-23 19:47:48 +00:00
if part_number > 10000 :
raise InvalidMaxPartNumberArgument ( part_number )
2021-08-21 14:05:40 +00:00
key = self . backend . upload_part (
bucket_name , upload_id , part_number , body
)
2014-03-30 15:50:36 +00:00
response = " "
2017-02-17 03:51:04 +00:00
response_headers . update ( key . response_dict )
return 200 , response_headers , response
2014-03-30 15:50:36 +00:00
storage_class = request . headers . get ( " x-amz-storage-class " , " STANDARD " )
2021-01-18 18:17:13 +00:00
encryption = request . headers . get ( " x-amz-server-side-encryption " , None )
kms_key_id = request . headers . get (
" x-amz-server-side-encryption-aws-kms-key-id " , None
)
2022-01-25 19:25:39 +00:00
2022-08-23 19:48:19 +00:00
checksum_algorithm = request . headers . get ( " x-amz-sdk-checksum-algorithm " , " " )
checksum_header = f " x-amz-checksum- { checksum_algorithm . lower ( ) } "
checksum_value = request . headers . get ( checksum_header )
if not checksum_value and checksum_algorithm :
2022-09-28 09:33:27 +00:00
# Extract the checksum-value from the body first
2023-03-16 11:56:20 +00:00
search = re . search ( rb " x-amz-checksum- \ w+:(.+= { 1,2}) " , body )
2022-08-23 19:48:19 +00:00
checksum_value = search . group ( 1 ) if search else None
if checksum_value :
2022-12-06 23:03:28 +00:00
# TODO: AWS computes the provided value and verifies it's the same
# Afterwards, it should be returned in every subsequent call
2023-03-16 11:56:20 +00:00
if isinstance ( checksum_value , bytes ) :
checksum_value = checksum_value . decode ( " utf-8 " )
2022-12-06 23:03:28 +00:00
response_headers . update ( { checksum_header : checksum_value } )
elif checksum_algorithm :
# If the value is not provided, we compute it and only return it as part of this request
checksum_value = compute_checksum ( body , algorithm = checksum_algorithm )
2022-08-23 19:48:19 +00:00
response_headers . update ( { checksum_header : checksum_value } )
2022-09-28 09:33:27 +00:00
# Extract the actual data from the body second
if (
request . headers . get ( " x-amz-content-sha256 " , None )
== " STREAMING-UNSIGNED-PAYLOAD-TRAILER "
) :
body = self . _handle_encoded_body (
body , int ( request . headers [ " x-amz-decoded-content-length " ] )
)
2021-01-18 18:17:13 +00:00
bucket_key_enabled = request . headers . get (
" x-amz-server-side-encryption-bucket-key-enabled " , None
)
if bucket_key_enabled is not None :
bucket_key_enabled = str ( bucket_key_enabled ) . lower ( )
2021-08-17 05:16:59 +00:00
bucket = self . backend . get_bucket ( bucket_name )
lock_enabled = bucket . object_lock_enabled
lock_mode = request . headers . get ( " x-amz-object-lock-mode " , None )
lock_until = request . headers . get ( " x-amz-object-lock-retain-until-date " , None )
2021-10-26 09:57:58 +00:00
legal_hold = request . headers . get ( " x-amz-object-lock-legal-hold " , None )
2021-08-17 05:16:59 +00:00
if lock_mode or lock_until or legal_hold == " ON " :
if not request . headers . get ( " Content-Md5 " ) :
raise InvalidContentMD5
if not lock_enabled :
raise LockNotEnabled
elif lock_enabled and bucket . has_default_lock :
if not request . headers . get ( " Content-Md5 " ) :
raise InvalidContentMD5
lock_until = bucket . default_retention ( )
lock_mode = bucket . default_lock_mode
2015-10-07 07:04:22 +00:00
acl = self . _acl_from_headers ( request . headers )
2017-09-21 02:04:23 +00:00
if acl is None :
acl = self . backend . get_bucket ( bucket_name ) . acl
2017-07-16 02:36:12 +00:00
tagging = self . _tagging_from_headers ( request . headers )
2014-03-30 15:50:36 +00:00
2021-08-17 05:16:59 +00:00
if " retention " in query :
if not lock_enabled :
raise LockNotEnabled
version_id = query . get ( " VersionId " )
2022-06-09 17:40:22 +00:00
retention = self . _mode_until_from_body ( )
2021-08-21 14:05:40 +00:00
self . backend . put_object_retention (
bucket_name , key_name , version_id = version_id , retention = retention
)
2021-08-17 05:16:59 +00:00
return 200 , response_headers , " "
if " legal-hold " in query :
if not lock_enabled :
raise LockNotEnabled
version_id = query . get ( " VersionId " )
legal_hold_status = self . _legal_hold_status_from_xml ( body )
2021-08-21 14:05:40 +00:00
self . backend . put_object_legal_hold (
bucket_name , key_name , version_id , legal_hold_status
)
2021-08-17 05:16:59 +00:00
return 200 , response_headers , " "
2015-10-07 07:04:22 +00:00
if " acl " in query :
2021-08-21 14:05:40 +00:00
self . backend . put_object_acl ( bucket_name , key_name , acl )
return 200 , response_headers , " "
2014-08-14 02:13:17 +00:00
2017-07-16 02:36:12 +00:00
if " tagging " in query :
2019-10-17 04:16:16 +00:00
if " versionId " in query :
version_id = query [ " versionId " ] [ 0 ]
else :
version_id = None
2023-04-20 16:47:39 +00:00
key_to_tag = self . backend . get_object (
bucket_name , key_name , version_id = version_id
)
2017-07-16 02:36:12 +00:00
tagging = self . _tagging_from_xml ( body )
2023-04-20 16:47:39 +00:00
self . backend . set_key_tags ( key_to_tag , tagging , key_name )
2017-07-16 02:36:12 +00:00
return 200 , response_headers , " "
2014-03-30 15:50:36 +00:00
if " x-amz-copy-source " in request . headers :
# Copy key
2018-05-03 08:40:49 +00:00
# you can have a quoted ?version=abc with a version Id, so work on
# we need to parse the unquoted string first
2021-11-01 22:17:06 +00:00
copy_source = request . headers . get ( " x-amz-copy-source " )
if isinstance ( copy_source , bytes ) :
copy_source = copy_source . decode ( " utf-8 " )
copy_source_parsed = urlparse ( copy_source )
2022-01-25 19:25:39 +00:00
src_bucket , src_key = (
unquote ( copy_source_parsed . path ) . lstrip ( " / " ) . split ( " / " , 1 )
)
2021-11-01 22:17:06 +00:00
src_version_id = parse_qs ( copy_source_parsed . query ) . get (
2023-04-20 16:47:39 +00:00
" versionId " , [ None ] # type: ignore
2021-11-01 22:17:06 +00:00
) [ 0 ]
2019-05-25 19:19:33 +00:00
2023-04-20 16:47:39 +00:00
key_to_copy = self . backend . get_object (
2022-01-25 19:25:39 +00:00
src_bucket , src_key , version_id = src_version_id , key_is_clean = True
2020-06-06 12:15:50 +00:00
)
2019-08-05 15:34:39 +00:00
2023-04-20 16:47:39 +00:00
if key_to_copy is not None :
if key_to_copy . storage_class in ARCHIVE_STORAGE_CLASSES :
if key_to_copy . response_dict . get (
2020-09-21 06:37:50 +00:00
" x-amz-restore "
2023-04-20 16:47:39 +00:00
) is None or ' ongoing-request= " true " ' in key_to_copy . response_dict . get ( # type: ignore
2020-09-21 06:37:50 +00:00
" x-amz-restore "
) :
2023-04-20 16:47:39 +00:00
raise ObjectNotInActiveTierError ( key_to_copy )
2020-09-21 06:37:50 +00:00
2022-01-25 19:25:39 +00:00
bucket_key_enabled = (
request . headers . get (
" x-amz-server-side-encryption-bucket-key-enabled " , " "
) . lower ( )
== " true "
)
2022-02-24 14:21:27 +00:00
mdirective = request . headers . get ( " x-amz-metadata-directive " )
2021-08-21 14:05:40 +00:00
self . backend . copy_object (
2023-04-20 16:47:39 +00:00
key_to_copy ,
2019-05-25 19:19:33 +00:00
bucket_name ,
key_name ,
storage = storage_class ,
acl = acl ,
2021-11-18 20:57:12 +00:00
kms_key_id = kms_key_id ,
encryption = encryption ,
2022-01-25 19:25:39 +00:00
bucket_key_enabled = bucket_key_enabled ,
2022-02-24 14:21:27 +00:00
mdirective = mdirective ,
2019-05-25 19:19:33 +00:00
)
else :
2021-11-14 17:16:58 +00:00
raise MissingKey ( key = src_key )
2019-05-25 19:19:33 +00:00
2023-04-20 16:47:39 +00:00
new_key : FakeKey = self . backend . get_object ( bucket_name , key_name ) # type: ignore
2014-03-30 15:50:36 +00:00
if mdirective is not None and mdirective == " REPLACE " :
2014-12-07 17:43:14 +00:00
metadata = metadata_from_headers ( request . headers )
new_key . set_metadata ( metadata , replace = True )
2019-03-28 14:10:57 +00:00
tdirective = request . headers . get ( " x-amz-tagging-directive " )
if tdirective == " REPLACE " :
tagging = self . _tagging_from_headers ( request . headers )
2020-03-31 11:04:04 +00:00
self . backend . set_key_tags ( new_key , tagging )
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_OBJECT_COPY_RESPONSE )
2017-02-17 03:51:04 +00:00
response_headers . update ( new_key . response_dict )
return 200 , response_headers , template . render ( key = new_key )
2021-08-17 05:16:59 +00:00
2022-01-18 15:18:57 +00:00
# Initial data
new_key = self . backend . put_object (
bucket_name ,
key_name ,
body ,
storage = storage_class ,
encryption = encryption ,
kms_key_id = kms_key_id ,
bucket_key_enabled = bucket_key_enabled ,
lock_mode = lock_mode ,
lock_legal_status = legal_hold ,
lock_until = lock_until ,
2023-03-16 11:56:20 +00:00
checksum_value = checksum_value ,
2022-01-18 15:18:57 +00:00
)
2021-08-17 05:16:59 +00:00
2022-01-18 15:18:57 +00:00
metadata = metadata_from_headers ( request . headers )
metadata . update ( metadata_from_headers ( query ) )
new_key . set_metadata ( metadata )
new_key . set_acl ( acl )
new_key . website_redirect_location = request . headers . get (
" x-amz-website-redirect-location "
)
2022-12-14 11:06:35 +00:00
if checksum_algorithm :
new_key . checksum_algorithm = checksum_algorithm
2022-01-18 15:18:57 +00:00
self . backend . set_key_tags ( new_key , tagging )
2014-03-30 15:50:36 +00:00
2017-02-17 03:51:04 +00:00
response_headers . update ( new_key . response_dict )
2020-04-06 20:01:43 +00:00
return 200 , response_headers , " "
2014-03-30 15:50:36 +00:00
2023-04-20 16:47:39 +00:00
def _key_response_head (
self ,
bucket_name : str ,
query : Dict [ str , Any ] ,
key_name : str ,
headers : Dict [ str , Any ] ,
) - > TYPE_RESPONSE :
2021-08-29 13:49:05 +00:00
self . _set_action ( " KEY " , " HEAD " , query )
self . _authenticate_and_authorize_s3_action ( )
2023-04-20 16:47:39 +00:00
response_headers : Dict [ str , Any ] = { }
2016-04-28 20:16:25 +00:00
version_id = query . get ( " versionId " , [ None ] ) [ 0 ]
2021-11-26 21:05:48 +00:00
if version_id and not self . backend . get_bucket ( bucket_name ) . is_versioned :
return 400 , response_headers , " "
2019-07-15 17:08:15 +00:00
part_number = query . get ( " partNumber " , [ None ] ) [ 0 ]
if part_number :
part_number = int ( part_number )
2017-05-19 22:59:25 +00:00
2017-05-23 18:29:01 +00:00
if_modified_since = headers . get ( " If-Modified-Since " , None )
2020-09-11 09:17:39 +00:00
if_match = headers . get ( " If-Match " , None )
if_none_match = headers . get ( " If-None-Match " , None )
if_unmodified_since = headers . get ( " If-Unmodified-Since " , None )
2017-05-19 22:59:25 +00:00
2021-08-21 14:05:40 +00:00
key = self . backend . head_object (
2019-07-15 17:08:15 +00:00
bucket_name , key_name , version_id = version_id , part_number = part_number
)
2014-03-30 15:50:36 +00:00
if key :
2017-02-17 03:51:04 +00:00
response_headers . update ( key . metadata )
response_headers . update ( key . response_dict )
2023-04-01 20:20:29 +00:00
response_headers . update ( { " AcceptRanges " : " bytes " } )
2017-05-19 22:59:25 +00:00
2020-09-11 09:17:39 +00:00
if if_unmodified_since :
if_unmodified_since = str_to_rfc_1123_datetime ( if_unmodified_since )
2022-11-16 21:12:48 +00:00
if key . last_modified . replace ( microsecond = 0 ) > if_unmodified_since :
2020-09-11 09:17:39 +00:00
return 412 , response_headers , " "
if if_match and key . etag != if_match :
return 412 , response_headers , " "
if if_modified_since :
if_modified_since = str_to_rfc_1123_datetime ( if_modified_since )
2022-11-16 21:12:48 +00:00
if key . last_modified . replace ( microsecond = 0 ) < = if_modified_since :
2020-09-11 09:17:39 +00:00
return 304 , response_headers , " Not Modified "
if if_none_match and key . etag == if_none_match :
2017-05-19 22:59:25 +00:00
return 304 , response_headers , " Not Modified "
2020-09-11 09:17:39 +00:00
2022-08-31 18:34:56 +00:00
if part_number :
full_key = self . backend . head_object ( bucket_name , key_name , version_id )
2023-04-20 16:47:39 +00:00
if full_key . multipart : # type: ignore
mp_part_count = str ( len ( full_key . multipart . partlist ) ) # type: ignore
2022-08-31 18:34:56 +00:00
response_headers [ " x-amz-mp-parts-count " ] = mp_part_count
2020-09-11 09:17:39 +00:00
return 200 , response_headers , " "
2014-03-30 15:50:36 +00:00
else :
2017-02-17 03:51:04 +00:00
return 404 , response_headers , " "
2014-03-30 15:50:36 +00:00
2023-04-20 16:47:39 +00:00
def _lock_config_from_body ( self ) - > Dict [ str , Any ] :
response_dict : Dict [ str , Any ] = {
" enabled " : False ,
" mode " : None ,
" days " : None ,
" years " : None ,
}
2022-06-09 17:40:22 +00:00
parsed_xml = xmltodict . parse ( self . body )
2021-08-17 05:16:59 +00:00
enabled = (
parsed_xml [ " ObjectLockConfiguration " ] [ " ObjectLockEnabled " ] == " Enabled "
)
2021-10-06 20:05:26 +00:00
response_dict [ " enabled " ] = enabled
2021-08-17 05:16:59 +00:00
2021-10-06 20:05:26 +00:00
default_retention = parsed_xml . get ( " ObjectLockConfiguration " ) . get ( " Rule " )
if default_retention :
default_retention = default_retention . get ( " DefaultRetention " )
mode = default_retention [ " Mode " ]
days = int ( default_retention . get ( " Days " , 0 ) )
years = int ( default_retention . get ( " Years " , 0 ) )
2021-08-17 05:16:59 +00:00
2021-10-06 20:05:26 +00:00
if days and years :
raise MalformedXML
response_dict [ " mode " ] = mode
response_dict [ " days " ] = days
response_dict [ " years " ] = years
2021-08-17 05:16:59 +00:00
2021-10-06 20:05:26 +00:00
return response_dict
2021-08-17 05:16:59 +00:00
2023-04-20 16:47:39 +00:00
def _acl_from_body ( self ) - > Optional [ FakeAcl ] :
2022-06-09 17:40:22 +00:00
parsed_xml = xmltodict . parse ( self . body )
2018-01-03 04:47:57 +00:00
if not parsed_xml . get ( " AccessControlPolicy " ) :
raise MalformedACLError ( )
# The owner is needed for some reason...
if not parsed_xml [ " AccessControlPolicy " ] . get ( " Owner " ) :
# TODO: Validate that the Owner is actually correct.
raise MalformedACLError ( )
# If empty, then no ACLs:
if parsed_xml [ " AccessControlPolicy " ] . get ( " AccessControlList " ) is None :
2023-04-20 16:47:39 +00:00
return None
2018-01-03 04:47:57 +00:00
if not parsed_xml [ " AccessControlPolicy " ] [ " AccessControlList " ] . get ( " Grant " ) :
raise MalformedACLError ( )
permissions = [ " READ " , " WRITE " , " READ_ACP " , " WRITE_ACP " , " FULL_CONTROL " ]
if not isinstance (
parsed_xml [ " AccessControlPolicy " ] [ " AccessControlList " ] [ " Grant " ] , list
) :
parsed_xml [ " AccessControlPolicy " ] [ " AccessControlList " ] [ " Grant " ] = [
parsed_xml [ " AccessControlPolicy " ] [ " AccessControlList " ] [ " Grant " ]
2019-10-31 15:44:26 +00:00
]
2018-01-03 04:47:57 +00:00
grants = self . _get_grants_from_xml (
parsed_xml [ " AccessControlPolicy " ] [ " AccessControlList " ] [ " Grant " ] ,
MalformedACLError ,
permissions ,
)
return FakeAcl ( grants )
2023-04-20 16:47:39 +00:00
def _get_grants_from_xml (
self ,
grant_list : List [ Dict [ str , Any ] ] ,
exception_type : Type [ S3ClientError ] ,
permissions : List [ str ] ,
) - > List [ FakeGrant ] :
2018-01-03 04:47:57 +00:00
grants = [ ]
for grant in grant_list :
if grant . get ( " Permission " , " " ) not in permissions :
raise exception_type ( )
if grant [ " Grantee " ] . get ( " @xsi:type " , " " ) not in [
" CanonicalUser " ,
" AmazonCustomerByEmail " ,
" Group " ,
] :
raise exception_type ( )
# TODO: Verify that the proper grantee data is supplied based on the type.
grants . append (
FakeGrant (
[
FakeGrantee (
2022-01-14 19:51:49 +00:00
grantee_id = grant [ " Grantee " ] . get ( " ID " , " " ) ,
2018-01-03 04:47:57 +00:00
display_name = grant [ " Grantee " ] . get ( " DisplayName " , " " ) ,
uri = grant [ " Grantee " ] . get ( " URI " , " " ) ,
)
2019-10-31 15:44:26 +00:00
] ,
2018-01-03 04:47:57 +00:00
[ grant [ " Permission " ] ] ,
2019-10-31 15:44:26 +00:00
)
2018-01-03 04:47:57 +00:00
)
return grants
2023-04-20 16:47:39 +00:00
def _acl_from_headers ( self , headers : Dict [ str , str ] ) - > Optional [ FakeAcl ] :
2015-10-07 07:04:22 +00:00
canned_acl = headers . get ( " x-amz-acl " , " " )
grants = [ ]
for header , value in headers . items ( ) :
2021-07-26 14:21:17 +00:00
header = header . lower ( )
2015-10-07 07:04:22 +00:00
if not header . startswith ( " x-amz-grant- " ) :
continue
permission = {
" read " : " READ " ,
" write " : " WRITE " ,
" read-acp " : " READ_ACP " ,
" write-acp " : " WRITE_ACP " ,
" full-control " : " FULL_CONTROL " ,
} [ header [ len ( " x-amz-grant- " ) : ] ]
grantees = [ ]
for key_and_value in value . split ( " , " ) :
2023-04-20 16:47:39 +00:00
key , value = re . match ( # type: ignore
2021-07-26 14:21:17 +00:00
' ([^=]+)= " ?([^ " ]+) " ? ' , key_and_value . strip ( )
2017-02-24 02:37:43 +00:00
) . groups ( )
2015-10-07 07:04:22 +00:00
if key . lower ( ) == " id " :
2022-01-14 19:51:49 +00:00
grantees . append ( FakeGrantee ( grantee_id = value ) )
2015-10-07 07:04:22 +00:00
else :
grantees . append ( FakeGrantee ( uri = value ) )
grants . append ( FakeGrant ( grantees , [ permission ] ) )
2021-11-09 22:49:37 +00:00
if canned_acl and grants :
raise S3AclAndGrantError ( )
if canned_acl :
return get_canned_acl ( canned_acl )
2015-10-07 07:04:22 +00:00
if grants :
return FakeAcl ( grants )
else :
2017-09-21 02:04:23 +00:00
return None
2015-10-07 07:04:22 +00:00
2023-04-20 16:47:39 +00:00
def _tagging_from_headers ( self , headers : Dict [ str , Any ] ) - > Dict [ str , str ] :
2020-04-01 14:35:25 +00:00
tags = { }
2017-07-16 02:36:12 +00:00
if headers . get ( " x-amz-tagging " ) :
parsed_header = parse_qs ( headers [ " x-amz-tagging " ] , keep_blank_values = True )
for tag in parsed_header . items ( ) :
2020-04-01 14:35:25 +00:00
tags [ tag [ 0 ] ] = tag [ 1 ] [ 0 ]
return tags
2017-07-16 02:36:12 +00:00
2023-04-20 16:47:39 +00:00
def _tagging_from_xml ( self , xml : bytes ) - > Dict [ str , str ] :
2017-11-03 06:03:54 +00:00
parsed_xml = xmltodict . parse ( xml , force_list = { " Tag " : True } )
2017-07-16 02:36:12 +00:00
2020-04-01 14:35:25 +00:00
tags = { }
2017-07-16 02:36:12 +00:00
for tag in parsed_xml [ " Tagging " ] [ " TagSet " ] [ " Tag " ] :
2020-04-01 14:35:25 +00:00
tags [ tag [ " Key " ] ] = tag [ " Value " ]
2017-07-16 02:36:12 +00:00
2020-04-01 14:35:25 +00:00
return tags
2017-07-16 02:36:12 +00:00
2023-04-20 16:47:39 +00:00
def _bucket_tagging_from_body ( self ) - > Dict [ str , str ] :
2022-06-09 17:40:22 +00:00
parsed_xml = xmltodict . parse ( self . body )
2017-09-07 18:30:05 +00:00
2020-04-01 14:35:25 +00:00
tags = { }
2017-09-07 18:30:05 +00:00
# Optional if no tags are being sent:
if parsed_xml [ " Tagging " ] . get ( " TagSet " ) :
2017-09-22 20:35:00 +00:00
# If there is only 1 tag, then it's not a list:
if not isinstance ( parsed_xml [ " Tagging " ] [ " TagSet " ] [ " Tag " ] , list ) :
2020-04-01 14:35:25 +00:00
tags [ parsed_xml [ " Tagging " ] [ " TagSet " ] [ " Tag " ] [ " Key " ] ] = parsed_xml [
" Tagging "
] [ " TagSet " ] [ " Tag " ] [ " Value " ]
2017-09-22 20:35:00 +00:00
else :
for tag in parsed_xml [ " Tagging " ] [ " TagSet " ] [ " Tag " ] :
2020-04-01 14:35:25 +00:00
if tag [ " Key " ] in tags :
raise DuplicateTagKeys ( )
tags [ tag [ " Key " ] ] = tag [ " Value " ]
2017-09-07 18:30:05 +00:00
2020-03-31 00:23:33 +00:00
# Verify that "aws:" is not in the tags. If so, then this is a problem:
2020-04-01 14:35:25 +00:00
for key , _ in tags . items ( ) :
if key . startswith ( " aws: " ) :
2020-03-31 00:23:33 +00:00
raise NoSystemTags ( )
2020-04-01 14:35:25 +00:00
return tags
2017-09-07 18:30:05 +00:00
2023-04-20 16:47:39 +00:00
def _cors_from_body ( self ) - > List [ Dict [ str , Any ] ] :
2022-06-09 17:40:22 +00:00
parsed_xml = xmltodict . parse ( self . body )
2017-09-07 18:30:05 +00:00
if isinstance ( parsed_xml [ " CORSConfiguration " ] [ " CORSRule " ] , list ) :
return [ cors for cors in parsed_xml [ " CORSConfiguration " ] [ " CORSRule " ] ]
return [ parsed_xml [ " CORSConfiguration " ] [ " CORSRule " ] ]
2023-04-20 16:47:39 +00:00
def _mode_until_from_body ( self ) - > Tuple [ Optional [ str ] , Optional [ str ] ] :
2022-06-09 17:40:22 +00:00
parsed_xml = xmltodict . parse ( self . body )
2021-08-17 05:16:59 +00:00
return (
2021-10-26 09:57:58 +00:00
parsed_xml . get ( " Retention " , None ) . get ( " Mode " , None ) ,
parsed_xml . get ( " Retention " , None ) . get ( " RetainUntilDate " , None ) ,
2021-08-17 05:16:59 +00:00
)
2023-04-20 16:47:39 +00:00
def _legal_hold_status_from_xml ( self , xml : bytes ) - > Dict [ str , Any ] :
2021-08-17 05:16:59 +00:00
parsed_xml = xmltodict . parse ( xml )
return parsed_xml [ " LegalHold " ] [ " Status " ]
2023-04-20 16:47:39 +00:00
def _encryption_config_from_body ( self ) - > Dict [ str , Any ] :
2022-06-09 17:40:22 +00:00
parsed_xml = xmltodict . parse ( self . body )
2020-05-27 16:21:03 +00:00
if (
not parsed_xml [ " ServerSideEncryptionConfiguration " ] . get ( " Rule " )
or not parsed_xml [ " ServerSideEncryptionConfiguration " ] [ " Rule " ] . get (
" ApplyServerSideEncryptionByDefault "
)
or not parsed_xml [ " ServerSideEncryptionConfiguration " ] [ " Rule " ] [
" ApplyServerSideEncryptionByDefault "
] . get ( " SSEAlgorithm " )
) :
raise MalformedXML ( )
2021-10-26 09:57:58 +00:00
return parsed_xml [ " ServerSideEncryptionConfiguration " ]
2020-05-27 16:21:03 +00:00
2023-04-20 16:47:39 +00:00
def _ownership_rule_from_body ( self ) - > Dict [ str , Any ] :
2022-08-24 10:48:13 +00:00
parsed_xml = xmltodict . parse ( self . body )
if not parsed_xml [ " OwnershipControls " ] [ " Rule " ] . get ( " ObjectOwnership " ) :
raise MalformedXML ( )
return parsed_xml [ " OwnershipControls " ] [ " Rule " ] [ " ObjectOwnership " ]
2023-04-20 16:47:39 +00:00
def _logging_from_body ( self ) - > Dict [ str , Any ] :
2022-06-09 17:40:22 +00:00
parsed_xml = xmltodict . parse ( self . body )
2018-01-03 04:47:57 +00:00
if not parsed_xml [ " BucketLoggingStatus " ] . get ( " LoggingEnabled " ) :
return { }
if not parsed_xml [ " BucketLoggingStatus " ] [ " LoggingEnabled " ] . get ( " TargetBucket " ) :
raise MalformedXML ( )
if not parsed_xml [ " BucketLoggingStatus " ] [ " LoggingEnabled " ] . get ( " TargetPrefix " ) :
parsed_xml [ " BucketLoggingStatus " ] [ " LoggingEnabled " ] [ " TargetPrefix " ] = " "
# Get the ACLs:
if parsed_xml [ " BucketLoggingStatus " ] [ " LoggingEnabled " ] . get ( " TargetGrants " ) :
permissions = [ " READ " , " WRITE " , " FULL_CONTROL " ]
if not isinstance (
parsed_xml [ " BucketLoggingStatus " ] [ " LoggingEnabled " ] [ " TargetGrants " ] [
" Grant "
] ,
list ,
) :
target_grants = self . _get_grants_from_xml (
[
parsed_xml [ " BucketLoggingStatus " ] [ " LoggingEnabled " ] [
" TargetGrants "
] [ " Grant " ]
] ,
MalformedXML ,
permissions ,
)
else :
target_grants = self . _get_grants_from_xml (
parsed_xml [ " BucketLoggingStatus " ] [ " LoggingEnabled " ] [ " TargetGrants " ] [
" Grant "
] ,
MalformedXML ,
permissions ,
)
parsed_xml [ " BucketLoggingStatus " ] [ " LoggingEnabled " ] [
" TargetGrants "
] = target_grants
return parsed_xml [ " BucketLoggingStatus " ] [ " LoggingEnabled " ]
2023-04-20 16:47:39 +00:00
def _notification_config_from_body ( self ) - > Dict [ str , Any ] :
2022-06-09 17:40:22 +00:00
parsed_xml = xmltodict . parse ( self . body )
2018-03-21 16:11:24 +00:00
if not len ( parsed_xml [ " NotificationConfiguration " ] ) :
return { }
# The types of notifications, and their required fields (apparently lambda is categorized by the API as
# "CloudFunction"):
notification_fields = [
( " Topic " , " sns " ) ,
( " Queue " , " sqs " ) ,
( " CloudFunction " , " lambda " ) ,
]
event_names = [
" s3:ReducedRedundancyLostObject " ,
" s3:ObjectCreated:* " ,
" s3:ObjectCreated:Put " ,
" s3:ObjectCreated:Post " ,
" s3:ObjectCreated:Copy " ,
" s3:ObjectCreated:CompleteMultipartUpload " ,
" s3:ObjectRemoved:* " ,
" s3:ObjectRemoved:Delete " ,
" s3:ObjectRemoved:DeleteMarkerCreated " ,
]
found_notifications = (
0 # Tripwire -- if this is not ever set, then there were no notifications
2019-10-31 15:44:26 +00:00
)
2018-03-21 16:11:24 +00:00
for name , arn_string in notification_fields :
# 1st verify that the proper notification configuration has been passed in (with an ARN that is close
# to being correct -- nothing too complex in the ARN logic):
the_notification = parsed_xml [ " NotificationConfiguration " ] . get (
2022-11-21 19:21:34 +00:00
f " { name } Configuration "
2019-10-31 15:44:26 +00:00
)
2018-03-21 16:11:24 +00:00
if the_notification :
found_notifications + = 1
if not isinstance ( the_notification , list ) :
the_notification = parsed_xml [ " NotificationConfiguration " ] [
2022-11-21 19:21:34 +00:00
f " { name } Configuration "
2018-03-21 16:11:24 +00:00
] = [ the_notification ]
for n in the_notification :
2022-11-21 19:21:34 +00:00
if not n [ name ] . startswith ( f " arn:aws: { arn_string } : " ) :
2018-03-21 16:11:24 +00:00
raise InvalidNotificationARN ( )
# 2nd, verify that the Events list is correct:
assert n [ " Event " ]
if not isinstance ( n [ " Event " ] , list ) :
n [ " Event " ] = [ n [ " Event " ] ]
for event in n [ " Event " ] :
if event not in event_names :
raise InvalidNotificationEvent ( )
# Parse out the filters:
if n . get ( " Filter " ) :
# Error if S3Key is blank:
if not n [ " Filter " ] [ " S3Key " ] :
raise KeyError ( )
if not isinstance ( n [ " Filter " ] [ " S3Key " ] [ " FilterRule " ] , list ) :
n [ " Filter " ] [ " S3Key " ] [ " FilterRule " ] = [
n [ " Filter " ] [ " S3Key " ] [ " FilterRule " ]
2019-10-31 15:44:26 +00:00
]
2018-03-21 16:11:24 +00:00
for filter_rule in n [ " Filter " ] [ " S3Key " ] [ " FilterRule " ] :
assert filter_rule [ " Name " ] in [ " suffix " , " prefix " ]
assert filter_rule [ " Value " ]
if not found_notifications :
return { }
return parsed_xml [ " NotificationConfiguration " ]
2023-04-20 16:47:39 +00:00
def _accelerate_config_from_body ( self ) - > str :
2022-06-09 17:40:22 +00:00
parsed_xml = xmltodict . parse ( self . body )
2019-05-25 10:19:00 +00:00
config = parsed_xml [ " AccelerateConfiguration " ]
return config [ " Status " ]
2023-04-20 16:47:39 +00:00
def _replication_config_from_xml ( self , xml : str ) - > Dict [ str , Any ] :
2021-10-16 17:26:09 +00:00
parsed_xml = xmltodict . parse ( xml , dict_constructor = dict )
config = parsed_xml [ " ReplicationConfiguration " ]
return config
2023-04-20 16:47:39 +00:00
def _key_response_delete (
self , headers : Any , bucket_name : str , query : Dict [ str , Any ] , key_name : str
) - > TYPE_RESPONSE :
2019-07-04 14:38:43 +00:00
self . _set_action ( " KEY " , " DELETE " , query )
2019-07-26 19:05:04 +00:00
self . _authenticate_and_authorize_s3_action ( )
2019-07-04 14:38:43 +00:00
2015-10-07 07:04:22 +00:00
if query . get ( " uploadId " ) :
2014-03-30 15:50:36 +00:00
upload_id = query [ " uploadId " ] [ 0 ]
2021-08-21 14:05:40 +00:00
self . backend . abort_multipart_upload ( bucket_name , upload_id )
2017-02-17 03:51:04 +00:00
return 204 , { } , " "
2017-05-14 16:56:25 +00:00
version_id = query . get ( " versionId " , [ None ] ) [ 0 ]
2020-06-20 11:15:29 +00:00
if " tagging " in query :
self . backend . delete_object_tagging (
bucket_name , key_name , version_id = version_id
)
template = self . response_template ( S3_DELETE_KEY_TAGGING_RESPONSE )
return 204 , { } , template . render ( version_id = version_id )
2021-10-06 20:05:26 +00:00
bypass = headers . get ( " X-Amz-Bypass-Governance-Retention " )
2022-01-14 19:51:49 +00:00
_ , response_meta = self . backend . delete_object (
2021-10-06 20:05:26 +00:00
bucket_name , key_name , version_id = version_id , bypass = bypass
2020-09-15 12:29:09 +00:00
)
response_headers = { }
if response_meta is not None :
for k in response_meta :
2022-11-21 19:21:34 +00:00
response_headers [ f " x-amz- { k } " ] = response_meta [ k ]
2020-09-15 12:29:09 +00:00
return 204 , response_headers , " "
2014-03-30 15:50:36 +00:00
2023-04-20 16:47:39 +00:00
def _complete_multipart_body ( self , body : bytes ) - > Iterator [ Tuple [ int , str ] ] :
2015-02-10 14:56:56 +00:00
ps = minidom . parseString ( body ) . getElementsByTagName ( " Part " )
prev = 0
for p in ps :
2017-02-24 02:37:43 +00:00
pn = int ( p . getElementsByTagName ( " PartNumber " ) [ 0 ] . firstChild . wholeText )
2015-02-10 14:56:56 +00:00
if pn < = prev :
raise InvalidPartOrder ( )
yield ( pn , p . getElementsByTagName ( " ETag " ) [ 0 ] . firstChild . wholeText )
2023-04-20 16:47:39 +00:00
def _key_response_post (
self ,
request : Any ,
body : bytes ,
bucket_name : str ,
query : Dict [ str , Any ] ,
key_name : str ,
) - > TYPE_RESPONSE :
2019-07-04 14:38:43 +00:00
self . _set_action ( " KEY " , " POST " , query )
2019-07-26 19:05:04 +00:00
self . _authenticate_and_authorize_s3_action ( )
2019-07-04 14:38:43 +00:00
2022-08-23 21:08:37 +00:00
encryption = request . headers . get ( " x-amz-server-side-encryption " )
kms_key_id = request . headers . get ( " x-amz-server-side-encryption-aws-kms-key-id " )
2015-10-07 07:04:22 +00:00
if body == b " " and " uploads " in query :
2022-08-23 21:08:37 +00:00
response_headers = { }
2014-12-07 17:43:14 +00:00
metadata = metadata_from_headers ( request . headers )
2021-11-09 19:49:29 +00:00
tagging = self . _tagging_from_headers ( request . headers )
2021-08-21 14:05:40 +00:00
storage_type = request . headers . get ( " x-amz-storage-class " , " STANDARD " )
2022-06-23 09:56:21 +00:00
acl = self . _acl_from_headers ( request . headers )
2022-08-23 21:08:37 +00:00
2021-08-21 14:05:40 +00:00
multipart_id = self . backend . create_multipart_upload (
2022-08-23 21:08:37 +00:00
bucket_name ,
key_name ,
metadata ,
storage_type ,
tagging ,
acl ,
encryption ,
kms_key_id ,
2021-08-21 14:05:40 +00:00
)
2022-08-23 21:08:37 +00:00
if encryption :
response_headers [ " x-amz-server-side-encryption " ] = encryption
if kms_key_id :
response_headers [
" x-amz-server-side-encryption-aws-kms-key-id "
] = kms_key_id
2014-12-07 17:43:14 +00:00
2014-12-12 20:46:07 +00:00
template = self . response_template ( S3_MULTIPART_INITIATE_RESPONSE )
2014-03-30 15:50:36 +00:00
response = template . render (
2021-08-21 14:05:40 +00:00
bucket_name = bucket_name , key_name = key_name , upload_id = multipart_id
2014-03-30 15:50:36 +00:00
)
2022-08-23 21:08:37 +00:00
return 200 , response_headers , response
2014-03-30 15:50:36 +00:00
2015-10-07 07:04:22 +00:00
if query . get ( " uploadId " ) :
2023-04-20 16:47:39 +00:00
multipart_id = query [ " uploadId " ] [ 0 ] # type: ignore
2021-07-26 14:21:17 +00:00
2021-08-21 14:05:40 +00:00
multipart , value , etag = self . backend . complete_multipart_upload (
2023-04-20 16:47:39 +00:00
bucket_name , multipart_id , self . _complete_multipart_body ( body )
2021-08-21 14:05:40 +00:00
)
2021-07-26 14:21:17 +00:00
if value is None :
return 400 , { } , " "
2021-08-21 14:05:40 +00:00
key = self . backend . put_object (
2021-07-26 14:21:17 +00:00
bucket_name ,
multipart . key_name ,
value ,
storage = multipart . storage ,
etag = etag ,
multipart = multipart ,
2022-08-23 21:08:37 +00:00
encryption = multipart . sse_encryption ,
kms_key_id = multipart . kms_key_id ,
2021-07-26 14:21:17 +00:00
)
key . set_metadata ( multipart . metadata )
2021-11-09 19:49:29 +00:00
self . backend . set_key_tags ( key , multipart . tags )
2022-06-23 09:56:21 +00:00
self . backend . put_object_acl ( bucket_name , key . name , multipart . acl )
2021-07-26 14:21:17 +00:00
2015-02-10 14:56:56 +00:00
template = self . response_template ( S3_MULTIPART_COMPLETE_RESPONSE )
2023-04-20 16:47:39 +00:00
headers : Dict [ str , Any ] = { }
2021-02-02 09:51:17 +00:00
if key . version_id :
headers [ " x-amz-version-id " ] = key . version_id
2022-08-23 21:08:37 +00:00
if key . encryption :
headers [ " x-amz-server-side-encryption " ] = key . encryption
if key . kms_key_id :
headers [ " x-amz-server-side-encryption-aws-kms-key-id " ] = key . kms_key_id
2021-02-02 09:51:17 +00:00
return (
200 ,
headers ,
template . render (
bucket_name = bucket_name , key_name = key . name , etag = key . etag
) ,
2015-02-10 14:56:56 +00:00
)
2021-07-26 14:21:17 +00:00
2015-10-07 07:04:22 +00:00
elif " restore " in query :
2014-03-30 15:50:36 +00:00
es = minidom . parseString ( body ) . getElementsByTagName ( " Days " )
days = es [ 0 ] . childNodes [ 0 ] . wholeText
2023-04-20 16:47:39 +00:00
key = self . backend . get_object ( bucket_name , key_name ) # type: ignore
2023-02-27 15:44:30 +00:00
if key . storage_class not in ARCHIVE_STORAGE_CLASSES :
2021-12-07 13:00:42 +00:00
raise InvalidObjectState ( storage_class = key . storage_class )
2014-03-30 15:50:36 +00:00
r = 202
if key . expiry_date is not None :
r = 200
key . restore ( int ( days ) )
2017-02-17 03:51:04 +00:00
return r , { } , " "
2023-03-21 16:55:19 +00:00
elif " select " in query :
request = xmltodict . parse ( body ) [ " SelectObjectContentRequest " ]
select_query = request [ " Expression " ]
input_details = request [ " InputSerialization " ]
output_details = request [ " OutputSerialization " ]
results = self . backend . select_object_content (
bucket_name , key_name , select_query , input_details , output_details
)
return 200 , { } , serialize_select ( results )
2021-07-26 14:21:17 +00:00
2013-03-26 14:52:33 +00:00
else :
2017-02-24 02:37:43 +00:00
raise NotImplementedError (
" Method POST had only been implemented for multipart uploads and restore operations, so far "
)
2023-04-20 16:47:39 +00:00
def _invalid_headers ( self , url : str , headers : Dict [ str , str ] ) - > bool :
2020-07-12 12:33:46 +00:00
"""
Verify whether the provided metadata in the URL is also present in the headers
: param url : . . . / file . txt & content - type = app % 2 Fjson & Signature = . .
: param headers : Content - Type = app / json
: return : True or False
"""
metadata_to_check = {
" content-disposition " : " Content-Disposition " ,
" content-encoding " : " Content-Encoding " ,
" content-language " : " Content-Language " ,
" content-length " : " Content-Length " ,
" content-md5 " : " Content-MD5 " ,
" content-type " : " Content-Type " ,
}
for url_key , header_key in metadata_to_check . items ( ) :
metadata_in_url = re . search ( url_key + " =(.+?)(&.+$|$) " , url )
if metadata_in_url :
url_value = unquote ( metadata_in_url . group ( 1 ) )
if header_key not in headers or ( url_value != headers [ header_key ] ) :
return True
return False
2013-02-18 21:09:40 +00:00
2022-06-09 17:40:22 +00:00
S3ResponseInstance = S3Response ( )
2013-02-18 21:09:40 +00:00
2013-02-18 22:31:15 +00:00
S3_ALL_BUCKETS = """ <ListAllMyBucketsResult xmlns= " http://s3.amazonaws.com/doc/2006-03-01 " >
< Owner >
< ID > bcaf1ffd86f41161ca5fb16fd081034f < / ID >
< DisplayName > webfile < / DisplayName >
< / Owner >
< Buckets >
{ % for bucket in buckets % }
< Bucket >
< Name > { { bucket . name } } < / Name >
2020-09-19 19:12:11 +00:00
< CreationDate > { { bucket . creation_date_ISO8601 } } < / CreationDate >
2013-02-18 22:31:15 +00:00
< / Bucket >
{ % endfor % }
< / Buckets >
< / ListAllMyBucketsResult > """
2013-02-27 06:12:11 +00:00
S3_BUCKET_GET_RESPONSE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< ListBucketResult xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< Name > { { bucket . name } } < / Name >
2019-12-21 11:08:13 +00:00
{ % if prefix != None % }
2013-02-27 06:12:11 +00:00
< Prefix > { { prefix } } < / Prefix >
2019-12-21 11:08:13 +00:00
{ % endif % }
2017-09-09 03:47:16 +00:00
< MaxKeys > { { max_keys } } < / MaxKeys >
2020-09-21 15:21:18 +00:00
{ % if delimiter % }
< Delimiter > { { delimiter } } < / Delimiter >
{ % endif % }
2022-06-01 11:03:40 +00:00
{ % if encoding_type % }
< EncodingType > { { encoding_type } } < / EncodingType >
{ % endif % }
2017-09-09 03:47:16 +00:00
< IsTruncated > { { is_truncated } } < / IsTruncated >
2019-10-21 01:40:05 +00:00
{ % if next_marker % }
< NextMarker > { { next_marker } } < / NextMarker >
{ % endif % }
2013-02-27 06:12:11 +00:00
{ % for key in result_keys % }
< Contents >
2022-06-01 21:26:27 +00:00
< Key > { { key . safe_name ( encoding_type ) } } < / Key >
2013-03-29 21:45:33 +00:00
< LastModified > { { key . last_modified_ISO8601 } } < / LastModified >
2013-02-27 06:12:11 +00:00
< ETag > { { key . etag } } < / ETag >
< Size > { { key . size } } < / Size >
2014-03-26 15:52:31 +00:00
< StorageClass > { { key . storage_class } } < / StorageClass >
2013-02-27 06:12:11 +00:00
< Owner >
< ID > 75 aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a < / ID >
< DisplayName > webfile < / DisplayName >
< / Owner >
< / Contents >
2017-02-09 02:21:43 +00:00
{ % endfor % }
{ % if delimiter % }
{ % for folder in result_folders % }
< CommonPrefixes >
< Prefix > { { folder } } < / Prefix >
< / CommonPrefixes >
{ % endfor % }
{ % endif % }
< / ListBucketResult > """
S3_BUCKET_GET_RESPONSE_V2 = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< ListBucketResult xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< Name > { { bucket . name } } < / Name >
2019-12-21 11:08:13 +00:00
{ % if prefix != None % }
2017-02-09 02:21:43 +00:00
< Prefix > { { prefix } } < / Prefix >
2019-12-21 11:08:13 +00:00
{ % endif % }
2017-02-09 02:21:43 +00:00
< MaxKeys > { { max_keys } } < / MaxKeys >
2019-08-20 07:01:37 +00:00
< KeyCount > { { key_count } } < / KeyCount >
2017-02-09 02:21:43 +00:00
{ % if delimiter % }
< Delimiter > { { delimiter } } < / Delimiter >
2022-06-01 11:03:40 +00:00
{ % endif % }
{ % if encoding_type % }
< EncodingType > { { encoding_type } } < / EncodingType >
2017-02-09 02:21:43 +00:00
{ % endif % }
< IsTruncated > { { is_truncated } } < / IsTruncated >
{ % if next_continuation_token % }
< NextContinuationToken > { { next_continuation_token } } < / NextContinuationToken >
{ % endif % }
{ % if start_after % }
< StartAfter > { { start_after } } < / StartAfter >
{ % endif % }
{ % for key in result_keys % }
< Contents >
2022-06-01 21:26:27 +00:00
< Key > { { key . safe_name ( encoding_type ) } } < / Key >
2017-02-09 02:21:43 +00:00
< LastModified > { { key . last_modified_ISO8601 } } < / LastModified >
< ETag > { { key . etag } } < / ETag >
< Size > { { key . size } } < / Size >
< StorageClass > { { key . storage_class } } < / StorageClass >
{ % if fetch_owner % }
< Owner >
< ID > 75 aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a < / ID >
< DisplayName > webfile < / DisplayName >
< / Owner >
{ % endif % }
2022-12-06 23:03:28 +00:00
{ % if key . checksum_algorithm % }
< ChecksumAlgorithm > { { key . checksum_algorithm } } < / ChecksumAlgorithm >
{ % endif % }
2017-02-09 02:21:43 +00:00
< / Contents >
2013-02-27 06:12:11 +00:00
{ % endfor % }
2013-04-13 23:23:32 +00:00
{ % if delimiter % }
{ % for folder in result_folders % }
< CommonPrefixes >
< Prefix > { { folder } } < / Prefix >
< / CommonPrefixes >
{ % endfor % }
{ % endif % }
2013-02-27 06:12:11 +00:00
< / ListBucketResult > """
2013-02-18 21:09:40 +00:00
S3_BUCKET_CREATE_RESPONSE = """ <CreateBucketResponse xmlns= " http://s3.amazonaws.com/doc/2006-03-01 " >
< CreateBucketResponse >
< Bucket > { { bucket . name } } < / Bucket >
< / CreateBucketResponse >
< / CreateBucketResponse > """
2013-02-18 22:17:19 +00:00
S3_DELETE_BUCKET_SUCCESS = """ <DeleteBucketResponse xmlns= " http://s3.amazonaws.com/doc/2006-03-01 " >
< DeleteBucketResponse >
< Code > 204 < / Code >
< Description > No Content < / Description >
< / DeleteBucketResponse >
< / DeleteBucketResponse > """
S3_DELETE_BUCKET_WITH_ITEMS_ERROR = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error > < Code > BucketNotEmpty < / Code >
< Message > The bucket you tried to delete is not empty < / Message >
< BucketName > { { bucket . name } } < / BucketName >
< RequestId > asdfasdfsdafds < / RequestId >
< HostId > sdfgdsfgdsfgdfsdsfgdfs < / HostId >
< / Error > """
2014-12-11 01:44:00 +00:00
S3_BUCKET_LOCATION = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
2018-11-20 18:50:42 +00:00
< LocationConstraint xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " > { % if location != None % } { { location } } { % endif % } < / LocationConstraint > """
2014-12-11 01:44:00 +00:00
2015-06-03 03:11:23 +00:00
S3_BUCKET_LIFECYCLE_CONFIGURATION = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< LifecycleConfiguration xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
{ % for rule in rules % }
< Rule >
< ID > { { rule . id } } < / ID >
2018-04-02 21:19:14 +00:00
{ % if rule . filter % }
< Filter >
2019-10-05 18:46:13 +00:00
{ % if rule . filter . prefix != None % }
2018-04-02 21:19:14 +00:00
< Prefix > { { rule . filter . prefix } } < / Prefix >
2019-10-05 18:46:13 +00:00
{ % endif % }
2020-04-01 14:35:25 +00:00
{ % if rule . filter . tag_key % }
2018-04-02 21:19:14 +00:00
< Tag >
2020-04-01 14:35:25 +00:00
< Key > { { rule . filter . tag_key } } < / Key >
< Value > { { rule . filter . tag_value } } < / Value >
2018-04-02 21:19:14 +00:00
< / Tag >
{ % endif % }
{ % if rule . filter . and_filter % }
< And >
2019-10-05 18:46:13 +00:00
{ % if rule . filter . and_filter . prefix != None % }
2018-04-02 21:19:14 +00:00
< Prefix > { { rule . filter . and_filter . prefix } } < / Prefix >
2019-10-05 18:46:13 +00:00
{ % endif % }
2020-04-01 14:35:25 +00:00
{ % for key , value in rule . filter . and_filter . tags . items ( ) % }
2018-04-02 21:19:14 +00:00
< Tag >
2020-04-01 14:35:25 +00:00
< Key > { { key } } < / Key >
< Value > { { value } } < / Value >
2018-04-02 21:19:14 +00:00
< / Tag >
{ % endfor % }
< / And >
{ % endif % }
< / Filter >
{ % else % }
2019-10-05 18:46:13 +00:00
{ % if rule . prefix != None % }
< Prefix > { { rule . prefix } } < / Prefix >
{ % endif % }
2018-04-02 21:19:14 +00:00
{ % endif % }
2015-06-03 03:11:23 +00:00
< Status > { { rule . status } } < / Status >
{ % if rule . storage_class % }
< Transition >
{ % if rule . transition_days % }
< Days > { { rule . transition_days } } < / Days >
{ % endif % }
{ % if rule . transition_date % }
< Date > { { rule . transition_date } } < / Date >
{ % endif % }
< StorageClass > { { rule . storage_class } } < / StorageClass >
< / Transition >
{ % endif % }
2018-04-02 21:19:14 +00:00
{ % if rule . expiration_days or rule . expiration_date or rule . expired_object_delete_marker % }
2015-06-03 03:11:23 +00:00
< Expiration >
{ % if rule . expiration_days % }
< Days > { { rule . expiration_days } } < / Days >
{ % endif % }
{ % if rule . expiration_date % }
< Date > { { rule . expiration_date } } < / Date >
{ % endif % }
2018-04-02 21:19:14 +00:00
{ % if rule . expired_object_delete_marker % }
< ExpiredObjectDeleteMarker > { { rule . expired_object_delete_marker } } < / ExpiredObjectDeleteMarker >
{ % endif % }
2015-06-03 03:11:23 +00:00
< / Expiration >
{ % endif % }
2018-10-03 05:40:28 +00:00
{ % if rule . nvt_noncurrent_days and rule . nvt_storage_class % }
< NoncurrentVersionTransition >
< NoncurrentDays > { { rule . nvt_noncurrent_days } } < / NoncurrentDays >
< StorageClass > { { rule . nvt_storage_class } } < / StorageClass >
< / NoncurrentVersionTransition >
{ % endif % }
{ % if rule . nve_noncurrent_days % }
< NoncurrentVersionExpiration >
< NoncurrentDays > { { rule . nve_noncurrent_days } } < / NoncurrentDays >
< / NoncurrentVersionExpiration >
{ % endif % }
{ % if rule . aimu_days % }
< AbortIncompleteMultipartUpload >
< DaysAfterInitiation > { { rule . aimu_days } } < / DaysAfterInitiation >
< / AbortIncompleteMultipartUpload >
{ % endif % }
2015-06-03 03:11:23 +00:00
< / Rule >
{ % endfor % }
< / LifecycleConfiguration >
"""
2015-06-12 06:56:14 +00:00
S3_BUCKET_VERSIONING = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
2014-06-27 17:34:00 +00:00
< VersioningConfiguration xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
2014-07-09 01:20:29 +00:00
< Status > { { bucket_versioning_status } } < / Status >
2014-06-27 17:34:00 +00:00
< / VersioningConfiguration >
"""
2015-06-12 06:56:14 +00:00
S3_BUCKET_GET_VERSIONING = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
2014-06-27 17:34:00 +00:00
{ % if status is none % }
2014-07-09 01:20:29 +00:00
< VersioningConfiguration xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " / >
2014-06-27 17:34:00 +00:00
{ % else % }
2014-07-09 01:20:29 +00:00
< VersioningConfiguration xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< Status > { { status } } < / Status >
< / VersioningConfiguration >
2014-06-27 17:34:00 +00:00
{ % endif % }
"""
2014-06-27 22:21:32 +00:00
S3_BUCKET_GET_VERSIONS = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< ListVersionsResult xmlns = " http://s3.amazonaws.com/doc/2006-03-01 " >
2014-07-09 01:20:29 +00:00
< Name > { { bucket . name } } < / Name >
2019-12-21 11:08:13 +00:00
{ % if prefix != None % }
2014-07-09 01:20:29 +00:00
< Prefix > { { prefix } } < / Prefix >
2019-12-21 11:08:13 +00:00
{ % endif % }
2021-10-14 18:13:40 +00:00
{ % if common_prefixes % }
{ % for prefix in common_prefixes % }
< CommonPrefixes >
< Prefix > { { prefix } } < / Prefix >
< / CommonPrefixes >
{ % endfor % }
{ % endif % }
< Delimiter > { { delimiter } } < / Delimiter >
< KeyMarker > { { key_marker or " " } } < / KeyMarker >
2014-07-09 01:20:29 +00:00
< MaxKeys > { { max_keys } } < / MaxKeys >
< IsTruncated > { { is_truncated } } < / IsTruncated >
{ % for key in key_list % }
< Version >
< Key > { { key . name } } < / Key >
2019-03-08 21:01:27 +00:00
< VersionId > { % if key . version_id is none % } null { % else % } { { key . version_id } } { % endif % } < / VersionId >
2021-10-14 18:13:40 +00:00
< IsLatest > { { ' true ' if key . is_latest else ' false ' } } < / IsLatest >
2014-07-09 01:20:29 +00:00
< LastModified > { { key . last_modified_ISO8601 } } < / LastModified >
< ETag > { { key . etag } } < / ETag >
< Size > { { key . size } } < / Size >
< StorageClass > { { key . storage_class } } < / StorageClass >
< Owner >
< ID > 75 aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a < / ID >
< DisplayName > webfile < / DisplayName >
< / Owner >
< / Version >
{ % endfor % }
2017-05-14 16:56:25 +00:00
{ % for marker in delete_marker_list % }
< DeleteMarker >
2018-01-05 20:12:45 +00:00
< Key > { { marker . name } } < / Key >
2017-05-14 16:56:25 +00:00
< VersionId > { { marker . version_id } } < / VersionId >
2021-10-14 18:13:40 +00:00
< IsLatest > { { ' true ' if marker . is_latest else ' false ' } } < / IsLatest >
2018-01-05 20:12:45 +00:00
< LastModified > { { marker . last_modified_ISO8601 } } < / LastModified >
2017-05-14 16:56:25 +00:00
< Owner >
< ID > 75 aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a < / ID >
< DisplayName > webfile < / DisplayName >
< / Owner >
< / DeleteMarker >
{ % endfor % }
2014-06-27 22:21:32 +00:00
< / ListVersionsResult >
"""
2014-05-06 21:21:33 +00:00
S3_DELETE_KEYS_RESPONSE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< DeleteResult xmlns = " http://s3.amazonaws.com/doc/2006-03-01 " >
2020-04-22 17:31:43 +00:00
{ % for k , v in deleted % }
2014-05-06 21:21:33 +00:00
< Deleted >
< Key > { { k } } < / Key >
2020-04-22 17:31:43 +00:00
{ % if v % } < VersionId > { { v } } < / VersionId > { % endif % }
2014-05-06 21:21:33 +00:00
< / Deleted >
{ % endfor % }
2022-05-12 13:59:47 +00:00
{ % for k , c , m in delete_errors % }
2014-05-06 21:21:33 +00:00
< Error >
< Key > { { k } } < / Key >
2022-05-12 13:59:47 +00:00
< Code > { { c } } < / Code >
< Message > { { m } } < / Message >
2014-05-06 21:21:33 +00:00
< / Error >
{ % endfor % }
< / DeleteResult > """
2020-06-20 11:15:29 +00:00
S3_DELETE_KEY_TAGGING_RESPONSE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< DeleteObjectTaggingResult xmlns = " http://s3.amazonaws.com/doc/2006-03-01 " >
< VersionId > { { version_id } } < / VersionId >
< / DeleteObjectTaggingResult >
"""
2015-10-07 07:04:22 +00:00
S3_OBJECT_ACL_RESPONSE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< AccessControlPolicy xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< Owner >
< ID > 75 aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a < / ID >
< DisplayName > webfile < / DisplayName >
< / Owner >
< AccessControlList >
2021-08-21 14:05:40 +00:00
{ % for grant in acl . grants % }
2015-10-07 07:04:22 +00:00
< Grant >
{ % for grantee in grant . grantees % }
< Grantee xmlns : xsi = " http://www.w3.org/2001/XMLSchema-instance "
xsi : type = " {{ grantee.type }} " >
{ % if grantee . uri % }
< URI > { { grantee . uri } } < / URI >
{ % endif % }
{ % if grantee . id % }
< ID > { { grantee . id } } < / ID >
{ % endif % }
{ % if grantee . display_name % }
< DisplayName > { { grantee . display_name } } < / DisplayName >
{ % endif % }
< / Grantee >
{ % endfor % }
{ % for permission in grant . permissions % }
< Permission > { { permission } } < / Permission >
{ % endfor % }
< / Grant >
{ % endfor % }
< / AccessControlList >
< / AccessControlPolicy > """
2021-10-06 20:05:26 +00:00
S3_OBJECT_LEGAL_HOLD = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< LegalHold >
< Status > { { legal_hold } } < / Status >
< / LegalHold >
"""
2017-07-16 02:36:12 +00:00
S3_OBJECT_TAGGING_RESPONSE = """ \
< ? xml version = " 1.0 " encoding = " UTF-8 " ? >
< Tagging xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
2017-09-07 18:30:05 +00:00
< TagSet >
2020-03-31 10:10:38 +00:00
{ % for tag in tags % }
2017-09-07 18:30:05 +00:00
< Tag >
2020-03-31 11:04:04 +00:00
< Key > { { tag . Key } } < / Key >
< Value > { { tag . Value } } < / Value >
2017-09-07 18:30:05 +00:00
< / Tag >
{ % endfor % }
< / TagSet >
< / Tagging > """
S3_BUCKET_CORS_RESPONSE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< CORSConfiguration >
2020-06-06 12:15:50 +00:00
{ % for cors in cors % }
2017-09-07 18:30:05 +00:00
< CORSRule >
{ % for origin in cors . allowed_origins % }
< AllowedOrigin > { { origin } } < / AllowedOrigin >
{ % endfor % }
{ % for method in cors . allowed_methods % }
< AllowedMethod > { { method } } < / AllowedMethod >
{ % endfor % }
{ % if cors . allowed_headers is not none % }
{ % for header in cors . allowed_headers % }
< AllowedHeader > { { header } } < / AllowedHeader >
{ % endfor % }
{ % endif % }
{ % if cors . exposed_headers is not none % }
{ % for header in cors . exposed_headers % }
< ExposedHeader > { { header } } < / ExposedHeader >
{ % endfor % }
{ % endif % }
{ % if cors . max_age_seconds is not none % }
< MaxAgeSeconds > { { cors . max_age_seconds } } < / MaxAgeSeconds >
{ % endif % }
< / CORSRule >
{ % endfor % }
< / CORSConfiguration >
"""
2016-06-13 20:24:09 +00:00
S3_OBJECT_COPY_RESPONSE = """ \
< CopyObjectResult xmlns = " http://doc.s3.amazonaws.com/2006-03-01 " >
2013-02-27 06:12:11 +00:00
< ETag > { { key . etag } } < / ETag >
2013-03-29 21:45:33 +00:00
< LastModified > { { key . last_modified_ISO8601 } } < / LastModified >
2016-06-13 20:24:09 +00:00
< / CopyObjectResult > """
2013-03-26 14:52:33 +00:00
2013-03-26 15:50:18 +00:00
S3_MULTIPART_INITIATE_RESPONSE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
2013-03-26 14:52:33 +00:00
< InitiateMultipartUploadResult xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< Bucket > { { bucket_name } } < / Bucket >
< Key > { { key_name } } < / Key >
< UploadId > { { upload_id } } < / UploadId >
< / InitiateMultipartUploadResult > """
2013-09-30 08:50:41 +00:00
S3_MULTIPART_UPLOAD_RESPONSE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< CopyPartResult xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< LastModified > { { part . last_modified_ISO8601 } } < / LastModified >
< ETag > { { part . etag } } < / ETag >
< / CopyPartResult > """
2013-09-30 09:09:35 +00:00
S3_MULTIPART_LIST_RESPONSE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< ListPartsResult xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< Bucket > { { bucket_name } } < / Bucket >
< Key > { { key_name } } < / Key >
< UploadId > { { upload_id } } < / UploadId >
< StorageClass > STANDARD < / StorageClass >
< Initiator >
< ID > 75 aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a < / ID >
< DisplayName > webfile < / DisplayName >
< / Initiator >
< Owner >
< ID > 75 aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a < / ID >
< DisplayName > webfile < / DisplayName >
< / Owner >
2021-08-28 06:38:16 +00:00
< PartNumberMarker > { { part_number_marker } } < / PartNumberMarker >
< NextPartNumberMarker > { { next_part_number_marker } } < / NextPartNumberMarker >
< MaxParts > { { max_parts } } < / MaxParts >
< IsTruncated > { { is_truncated } } < / IsTruncated >
2013-09-30 09:09:35 +00:00
{ % for part in parts % }
< Part >
< PartNumber > { { part . name } } < / PartNumber >
< LastModified > { { part . last_modified_ISO8601 } } < / LastModified >
< ETag > { { part . etag } } < / ETag >
< Size > { { part . size } } < / Size >
< / Part >
{ % endfor % }
< / ListPartsResult > """
2013-03-26 15:50:18 +00:00
S3_MULTIPART_COMPLETE_RESPONSE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< CompleteMultipartUploadResult xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< Location > http : / / { { bucket_name } } . s3 . amazonaws . com / { { key_name } } < / Location >
< Bucket > { { bucket_name } } < / Bucket >
< Key > { { key_name } } < / Key >
< ETag > { { etag } } < / ETag >
< / CompleteMultipartUploadResult >
2013-03-26 14:52:33 +00:00
"""
2013-09-30 11:35:24 +00:00
2022-08-13 09:49:43 +00:00
S3_ALL_MULTIPARTS = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
2014-04-02 16:03:40 +00:00
< ListMultipartUploadsResult xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< Bucket > { { bucket_name } } < / Bucket >
< KeyMarker > < / KeyMarker >
< UploadIdMarker > < / UploadIdMarker >
< MaxUploads > 1000 < / MaxUploads >
2021-07-26 14:21:17 +00:00
< IsTruncated > false < / IsTruncated >
2014-04-02 16:03:40 +00:00
{ % for upload in uploads % }
< Upload >
< Key > { { upload . key_name } } < / Key >
< UploadId > { { upload . id } } < / UploadId >
< Initiator >
2022-08-13 09:49:43 +00:00
< ID > arn : aws : iam : : { { account_id } } : user / user1 - 11111 a31 - 17 b5 - 4 fb7 - 9 df5 - b111111f13de < / ID >
2014-04-02 16:03:40 +00:00
< DisplayName > user1 - 11111 a31 - 17 b5 - 4 fb7 - 9 df5 - b111111f13de < / DisplayName >
< / Initiator >
< Owner >
< ID > 75 aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a < / ID >
2015-10-07 07:04:22 +00:00
< DisplayName > webfile < / DisplayName >
2014-04-02 16:03:40 +00:00
< / Owner >
< StorageClass > STANDARD < / StorageClass >
< Initiated > 2010 - 11 - 10 T20 : 48 : 33.000 Z < / Initiated >
< / Upload >
{ % endfor % }
< / ListMultipartUploadsResult >
"""
2015-07-23 21:33:52 +00:00
S3_NO_POLICY = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > NoSuchBucketPolicy < / Code >
< Message > The bucket policy does not exist < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > 0 D68A23BB2E2215B < / RequestId >
< HostId > 9 Gjjt1m + cjU4OPvX9O9 / 8 RuvnG41MRb / 18 Oux2o5H5MY7ISNTlXN + Dz9IG62 / ILVxhAGI0qyPfg = < / HostId >
< / Error >
"""
2017-09-07 18:30:05 +00:00
S3_NO_LIFECYCLE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > NoSuchLifecycleConfiguration < / Code >
< Message > The lifecycle configuration does not exist < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > 44425877 V1D0A2F9 < / RequestId >
< HostId > 9 Gjjt1m + cjU4OPvX9O9 / 8 RuvnG41MRb / 18 Oux2o5H5MY7ISNTlXN + Dz9IG62 / ILVxhAGI0qyPfg = < / HostId >
< / Error >
"""
S3_NO_BUCKET_TAGGING = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > NoSuchTagSet < / Code >
< Message > The TagSet does not exist < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > 44425877 V1D0A2F9 < / RequestId >
< HostId > 9 Gjjt1m + cjU4OPvX9O9 / 8 RuvnG41MRb / 18 Oux2o5H5MY7ISNTlXN + Dz9IG62 / ILVxhAGI0qyPfg = < / HostId >
< / Error >
"""
S3_NO_BUCKET_WEBSITE_CONFIG = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > NoSuchWebsiteConfiguration < / Code >
< Message > The specified bucket does not have a website configuration < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > 44425877 V1D0A2F9 < / RequestId >
< HostId > 9 Gjjt1m + cjU4OPvX9O9 / 8 RuvnG41MRb / 18 Oux2o5H5MY7ISNTlXN + Dz9IG62 / ILVxhAGI0qyPfg = < / HostId >
< / Error >
"""
S3_INVALID_CORS_REQUEST = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > NoSuchWebsiteConfiguration < / Code >
< Message > The specified bucket does not have a website configuration < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > 44425877 V1D0A2F9 < / RequestId >
< HostId > 9 Gjjt1m + cjU4OPvX9O9 / 8 RuvnG41MRb / 18 Oux2o5H5MY7ISNTlXN + Dz9IG62 / ILVxhAGI0qyPfg = < / HostId >
< / Error >
"""
S3_NO_CORS_CONFIG = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > NoSuchCORSConfiguration < / Code >
< Message > The CORS configuration does not exist < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > 44425877 V1D0A2F9 < / RequestId >
< HostId > 9 Gjjt1m + cjU4OPvX9O9 / 8 RuvnG41MRb / 18 Oux2o5H5MY7ISNTlXN + Dz9IG62 / ILVxhAGI0qyPfg = < / HostId >
< / Error >
"""
2018-01-03 04:47:57 +00:00
S3_LOGGING_CONFIG = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< BucketLoggingStatus xmlns = " http://doc.s3.amazonaws.com/2006-03-01 " >
< LoggingEnabled >
< TargetBucket > { { logging [ " TargetBucket " ] } } < / TargetBucket >
< TargetPrefix > { { logging [ " TargetPrefix " ] } } < / TargetPrefix >
{ % if logging . get ( " TargetGrants " ) % }
< TargetGrants >
{ % for grant in logging [ " TargetGrants " ] % }
< Grant >
< Grantee xmlns : xsi = " http://www.w3.org/2001/XMLSchema-instance "
xsi : type = " {{ grant.grantees[0].type }} " >
{ % if grant . grantees [ 0 ] . uri % }
< URI > { { grant . grantees [ 0 ] . uri } } < / URI >
{ % endif % }
{ % if grant . grantees [ 0 ] . id % }
< ID > { { grant . grantees [ 0 ] . id } } < / ID >
{ % endif % }
{ % if grant . grantees [ 0 ] . display_name % }
< DisplayName > { { grant . grantees [ 0 ] . display_name } } < / DisplayName >
{ % endif % }
< / Grantee >
< Permission > { { grant . permissions [ 0 ] } } < / Permission >
< / Grant >
{ % endfor % }
< / TargetGrants >
{ % endif % }
< / LoggingEnabled >
< / BucketLoggingStatus >
"""
S3_NO_LOGGING_CONFIG = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< BucketLoggingStatus xmlns = " http://doc.s3.amazonaws.com/2006-03-01 " / >
"""
2018-03-21 16:11:24 +00:00
2020-05-27 16:21:03 +00:00
S3_ENCRYPTION_CONFIG = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
2021-07-26 14:21:17 +00:00
< ServerSideEncryptionConfiguration xmlns = " http://doc.s3.amazonaws.com/2006-03-01 " >
2021-10-26 09:57:58 +00:00
{ % if encryption % }
2020-05-27 16:21:03 +00:00
< Rule >
< ApplyServerSideEncryptionByDefault >
2021-10-26 09:57:58 +00:00
< SSEAlgorithm > { { encryption [ " Rule " ] [ " ApplyServerSideEncryptionByDefault " ] [ " SSEAlgorithm " ] } } < / SSEAlgorithm >
{ % if encryption [ " Rule " ] [ " ApplyServerSideEncryptionByDefault " ] . get ( " KMSMasterKeyID " ) % }
< KMSMasterKeyID > { { encryption [ " Rule " ] [ " ApplyServerSideEncryptionByDefault " ] [ " KMSMasterKeyID " ] } } < / KMSMasterKeyID >
2020-05-27 16:21:03 +00:00
{ % endif % }
< / ApplyServerSideEncryptionByDefault >
2021-10-26 09:57:58 +00:00
< BucketKeyEnabled > { { ' true ' if encryption [ " Rule " ] . get ( " BucketKeyEnabled " ) == ' true ' else ' false ' } } < / BucketKeyEnabled >
2020-05-27 16:21:03 +00:00
< / Rule >
2021-10-26 09:57:58 +00:00
{ % endif % }
2021-07-26 14:21:17 +00:00
< / ServerSideEncryptionConfiguration >
2020-05-27 16:21:03 +00:00
"""
2020-07-12 12:33:46 +00:00
S3_INVALID_PRESIGNED_PARAMETERS = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > SignatureDoesNotMatch < / Code >
< Message > The request signature we calculated does not match the signature you provided . Check your key and signing method . < / Message >
< RequestId > 0 D68A23BB2E2215B < / RequestId >
< HostId > 9 Gjjt1m + cjU4OPvX9O9 / 8 RuvnG41MRb / 18 Oux2o5H5MY7ISNTlXN + Dz9IG62 / ILVxhAGI0qyPfg = < / HostId >
< / Error >
"""
2020-05-27 16:21:03 +00:00
S3_NO_ENCRYPTION = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > ServerSideEncryptionConfigurationNotFoundError < / Code >
< Message > The server side encryption configuration was not found < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > 0 D68A23BB2E2215B < / RequestId >
< HostId > 9 Gjjt1m + cjU4OPvX9O9 / 8 RuvnG41MRb / 18 Oux2o5H5MY7ISNTlXN + Dz9IG62 / ILVxhAGI0qyPfg = < / HostId >
< / Error >
"""
2018-03-21 16:11:24 +00:00
S3_GET_BUCKET_NOTIFICATION_CONFIG = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< NotificationConfiguration xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
2020-06-06 12:15:50 +00:00
{ % for topic in config . topic % }
2018-03-21 16:11:24 +00:00
< TopicConfiguration >
< Id > { { topic . id } } < / Id >
< Topic > { { topic . arn } } < / Topic >
{ % for event in topic . events % }
< Event > { { event } } < / Event >
{ % endfor % }
{ % if topic . filters % }
< Filter >
< S3Key >
{ % for rule in topic . filters [ " S3Key " ] [ " FilterRule " ] % }
< FilterRule >
< Name > { { rule [ " Name " ] } } < / Name >
< Value > { { rule [ " Value " ] } } < / Value >
< / FilterRule >
{ % endfor % }
< / S3Key >
< / Filter >
{ % endif % }
< / TopicConfiguration >
{ % endfor % }
2020-06-06 12:15:50 +00:00
{ % for queue in config . queue % }
2018-03-21 16:11:24 +00:00
< QueueConfiguration >
< Id > { { queue . id } } < / Id >
< Queue > { { queue . arn } } < / Queue >
{ % for event in queue . events % }
< Event > { { event } } < / Event >
{ % endfor % }
{ % if queue . filters % }
< Filter >
< S3Key >
{ % for rule in queue . filters [ " S3Key " ] [ " FilterRule " ] % }
< FilterRule >
< Name > { { rule [ " Name " ] } } < / Name >
< Value > { { rule [ " Value " ] } } < / Value >
< / FilterRule >
{ % endfor % }
< / S3Key >
< / Filter >
{ % endif % }
< / QueueConfiguration >
{ % endfor % }
2020-06-06 12:15:50 +00:00
{ % for cf in config . cloud_function % }
2018-03-21 16:11:24 +00:00
< CloudFunctionConfiguration >
< Id > { { cf . id } } < / Id >
< CloudFunction > { { cf . arn } } < / CloudFunction >
{ % for event in cf . events % }
< Event > { { event } } < / Event >
{ % endfor % }
{ % if cf . filters % }
< Filter >
< S3Key >
{ % for rule in cf . filters [ " S3Key " ] [ " FilterRule " ] % }
< FilterRule >
< Name > { { rule [ " Name " ] } } < / Name >
< Value > { { rule [ " Value " ] } } < / Value >
< / FilterRule >
{ % endfor % }
< / S3Key >
< / Filter >
{ % endif % }
< / CloudFunctionConfiguration >
{ % endfor % }
< / NotificationConfiguration >
"""
2019-05-25 10:19:00 +00:00
S3_BUCKET_ACCELERATE = """
< AccelerateConfiguration xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< Status > { { bucket . accelerate_configuration } } < / Status >
< / AccelerateConfiguration >
"""
S3_BUCKET_ACCELERATE_NOT_SET = """
< AccelerateConfiguration xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " / >
"""
2019-12-10 01:38:26 +00:00
S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION = """
< PublicAccessBlockConfiguration >
< BlockPublicAcls > { { public_block_config . block_public_acls } } < / BlockPublicAcls >
< IgnorePublicAcls > { { public_block_config . ignore_public_acls } } < / IgnorePublicAcls >
< BlockPublicPolicy > { { public_block_config . block_public_policy } } < / BlockPublicPolicy >
< RestrictPublicBuckets > { { public_block_config . restrict_public_buckets } } < / RestrictPublicBuckets >
< / PublicAccessBlockConfiguration >
"""
2021-08-17 05:16:59 +00:00
S3_BUCKET_LOCK_CONFIGURATION = """
< ObjectLockConfiguration xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
{ % if lock_enabled % }
< ObjectLockEnabled > Enabled < / ObjectLockEnabled >
{ % else % }
< ObjectLockEnabled > Disabled < / ObjectLockEnabled >
{ % endif % }
2021-10-06 20:05:26 +00:00
{ % if mode % }
2021-08-17 05:16:59 +00:00
< Rule >
< DefaultRetention >
2021-10-06 20:05:26 +00:00
< Mode > { { mode } } < / Mode >
< Days > { { days } } < / Days >
< Years > { { years } } < / Years >
2021-08-17 05:16:59 +00:00
< / DefaultRetention >
2021-10-06 20:05:26 +00:00
< / Rule >
{ % endif % }
2021-08-17 05:16:59 +00:00
< / ObjectLockConfiguration >
"""
2021-09-01 15:30:01 +00:00
S3_DUPLICATE_BUCKET_ERROR = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > BucketAlreadyOwnedByYou < / Code >
< Message > Your previous request to create the named bucket succeeded and you already own it . < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > 44425877 V1D0A2F9 < / RequestId >
< HostId > 9 Gjjt1m + cjU4OPvX9O9 / 8 RuvnG41MRb / 18 Oux2o5H5MY7ISNTlXN + Dz9IG62 / ILVxhAGI0qyPfg = < / HostId >
< / Error >
"""
2021-10-16 17:26:09 +00:00
S3_NO_REPLICATION = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > ReplicationConfigurationNotFoundError < / Code >
< Message > The replication configuration was not found < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > ZM6MA8EGCZ1M9EW9 < / RequestId >
< HostId > SMUZFedx1CuwjSaZQnM2bEVpet8UgX9uD / L7e MlldClgtEICTTVFz3C66cz8Bssci2OsWCVlog = < / HostId >
< / Error >
"""
S3_NO_VERSIONING_ENABLED = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< Error >
< Code > InvalidRequest < / Code >
< Message > Versioning must be ' Enabled ' on the bucket to apply a replication configuration < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > ZM6MA8EGCZ1M9EW9 < / RequestId >
< HostId > SMUZFedx1CuwjSaZQnM2bEVpet8UgX9uD / L7e MlldClgtEICTTVFz3C66cz8Bssci2OsWCVlog = < / HostId >
< / Error >
"""
S3_REPLICATION_CONFIG = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< ReplicationConfiguration xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
{ % for rule in replication [ " Rule " ] % }
< Rule >
< ID > { { rule [ " ID " ] } } < / ID >
< Priority > { { rule [ " Priority " ] } } < / Priority >
< Status > { { rule [ " Status " ] } } < / Status >
< DeleteMarkerReplication >
< Status > Disabled < / Status >
< / DeleteMarkerReplication >
< Filter >
< Prefix > < / Prefix >
< / Filter >
< Destination >
< Bucket > { { rule [ " Destination " ] [ " Bucket " ] } } < / Bucket >
< / Destination >
< / Rule >
{ % endfor % }
< Role > { { replication [ " Role " ] } } < / Role >
< / ReplicationConfiguration >
"""
2022-08-24 10:48:13 +00:00
S3_BUCKET_GET_OWNERSHIP_RULE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< OwnershipControls xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
< Rule >
< ObjectOwnership > { { ownership_rule } } < / ObjectOwnership >
< / Rule >
< / OwnershipControls >
"""
S3_ERROR_BUCKET_ONWERSHIP_NOT_FOUND = """
< Error >
< Code > OwnershipControlsNotFoundError < / Code >
< Message > The bucket ownership controls were not found < / Message >
< BucketName > { { bucket_name } } < / BucketName >
< RequestId > 294 PFVCB9GFVXY2S < / RequestId >
< HostId > l / tqqyk7HZbfvFFpdq3 + CAzA9JXUiV4ZajKYhwolOIpnmlvZrsI88AKsDLsgQI6EvZ9MuGHhk7M = < / HostId >
< / Error >
"""
2023-03-16 11:56:20 +00:00
S3_OBJECT_ATTRIBUTES_RESPONSE = """ <?xml version= " 1.0 " encoding= " UTF-8 " ?>
< GetObjectAttributesOutput xmlns = " http://s3.amazonaws.com/doc/2006-03-01/ " >
{ % if etag is not none % } < ETag > { { etag } } < / ETag > { % endif % }
{ % if checksum is not none % }
< Checksum >
{ % if " CRC32 " in checksum % } < ChecksumCRC32 > { { checksum [ " CRC32 " ] } } < / ChecksumCRC32 > { % endif % }
{ % if " CRC32C " in checksum % } < ChecksumCRC32C > { { checksum [ " CRC32C " ] } } < / ChecksumCRC32C > { % endif % }
{ % if " SHA1 " in checksum % } < ChecksumSHA1 > { { checksum [ " SHA1 " ] } } < / ChecksumSHA1 > { % endif % }
{ % if " SHA256 " in checksum % } < ChecksumSHA256 > { { checksum [ " SHA256 " ] } } < / ChecksumSHA256 > { % endif % }
< / Checksum >
{ % endif % }
{ % if size is not none % } < ObjectSize > { { size } } < / ObjectSize > { % endif % }
{ % if storage_class is not none % } < StorageClass > { { storage_class } } < / StorageClass > { % endif % }
< / GetObjectAttributesOutput >
"""