diff options
author | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
---|---|---|
committer | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
commit | 4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch) | |
tree | ee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/botocore/handlers.py | |
parent | cc961e04ba734dd72309fb548a2f97d67d578813 (diff) | |
download | gn-ai-master.tar.gz |
Diffstat (limited to '.venv/lib/python3.12/site-packages/botocore/handlers.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/botocore/handlers.py | 1586 |
1 files changed, 1586 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/botocore/handlers.py b/.venv/lib/python3.12/site-packages/botocore/handlers.py new file mode 100644 index 00000000..29a8680a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/botocore/handlers.py @@ -0,0 +1,1586 @@ +# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +"""Builtin event handlers. + +This module contains builtin handlers for events emitted by botocore. +""" + +import base64 +import copy +import logging +import os +import re +import uuid +import warnings +from io import BytesIO + +import botocore +import botocore.auth +from botocore import utils +from botocore.compat import ( + ETree, + OrderedDict, + XMLParseError, + ensure_bytes, + get_md5, + json, + quote, + unquote, + unquote_str, + urlsplit, + urlunsplit, +) +from botocore.docs.utils import ( + AppendParamDocumentation, + AutoPopulatedParam, + HideParamFromOperations, +) +from botocore.endpoint_provider import VALID_HOST_LABEL_RE +from botocore.exceptions import ( + AliasConflictParameterError, + ParamValidationError, + UnsupportedTLSVersionWarning, +) +from botocore.regions import EndpointResolverBuiltins +from botocore.signers import ( + add_dsql_generate_db_auth_token_methods, + add_generate_db_auth_token, + add_generate_presigned_post, + add_generate_presigned_url, +) +from botocore.utils import ( + SAFE_CHARS, + ArnParser, + percent_encode, + switch_host_with_param, +) + +# Keep these imported. There's pre-existing code that uses them. +from botocore import retryhandler # noqa +from botocore import translate # noqa +from botocore.compat import MD5_AVAILABLE # noqa +from botocore.exceptions import MissingServiceIdError # noqa +from botocore.utils import hyphenize_service_id # noqa +from botocore.utils import is_global_accesspoint # noqa +from botocore.utils import SERVICE_NAME_ALIASES # noqa + + +logger = logging.getLogger(__name__) + +REGISTER_FIRST = object() +REGISTER_LAST = object() +# From the S3 docs: +# The rules for bucket names in the US Standard region allow bucket names +# to be as long as 255 characters, and bucket names can contain any +# combination of uppercase letters, lowercase letters, numbers, periods +# (.), hyphens (-), and underscores (_). +VALID_BUCKET = re.compile(r'^[a-zA-Z0-9.\-_]{1,255}$') +_ACCESSPOINT_ARN = ( + r'^arn:(aws).*:(s3|s3-object-lambda):[a-z\-0-9]*:[0-9]{12}:accesspoint[/:]' + r'[a-zA-Z0-9\-.]{1,63}$' +) +_OUTPOST_ARN = ( + r'^arn:(aws).*:s3-outposts:[a-z\-0-9]+:[0-9]{12}:outpost[/:]' + r'[a-zA-Z0-9\-]{1,63}[/:]accesspoint[/:][a-zA-Z0-9\-]{1,63}$' +) +VALID_S3_ARN = re.compile('|'.join([_ACCESSPOINT_ARN, _OUTPOST_ARN])) +# signing names used for the services s3 and s3-control, for example in +# botocore/data/s3/2006-03-01/endpoints-rule-set-1.json +S3_SIGNING_NAMES = ('s3', 's3-outposts', 's3-object-lambda', 's3express') +VERSION_ID_SUFFIX = re.compile(r'\?versionId=[^\s]+$') + + +def handle_service_name_alias(service_name, **kwargs): + return SERVICE_NAME_ALIASES.get(service_name, service_name) + + +def add_recursion_detection_header(params, **kwargs): + has_lambda_name = 'AWS_LAMBDA_FUNCTION_NAME' in os.environ + trace_id = os.environ.get('_X_AMZN_TRACE_ID') + if has_lambda_name and trace_id: + headers = params['headers'] + if 'X-Amzn-Trace-Id' not in headers: + headers['X-Amzn-Trace-Id'] = quote(trace_id, safe='-=;:+&[]{}"\',') + + +def escape_xml_payload(params, **kwargs): + # Replace \r and \n with the escaped sequence over the whole XML document + # to avoid linebreak normalization modifying customer input when the + # document is parsed. Ideally, we would do this in ElementTree.tostring, + # but it doesn't allow us to override entity escaping for text fields. For + # this operation \r and \n can only appear in the XML document if they were + # passed as part of the customer input. + body = params['body'] + if b'\r' in body: + body = body.replace(b'\r', b'
') + if b'\n' in body: + body = body.replace(b'\n', b'
') + + params['body'] = body + + +def check_for_200_error(response, **kwargs): + """This function has been deprecated, but is kept for backwards compatibility.""" + # From: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html + # There are two opportunities for a copy request to return an error. One + # can occur when Amazon S3 receives the copy request and the other can + # occur while Amazon S3 is copying the files. If the error occurs before + # the copy operation starts, you receive a standard Amazon S3 error. If the + # error occurs during the copy operation, the error response is embedded in + # the 200 OK response. This means that a 200 OK response can contain either + # a success or an error. Make sure to design your application to parse the + # contents of the response and handle it appropriately. + # + # So this handler checks for this case. Even though the server sends a + # 200 response, conceptually this should be handled exactly like a + # 500 response (with respect to raising exceptions, retries, etc.) + # We're connected *before* all the other retry logic handlers, so as long + # as we switch the error code to 500, we'll retry the error as expected. + if response is None: + # A None response can happen if an exception is raised while + # trying to retrieve the response. See Endpoint._get_response(). + return + http_response, parsed = response + if _looks_like_special_case_error( + http_response.status_code, http_response.content + ): + logger.debug( + "Error found for response with 200 status code, " + "errors: %s, changing status code to " + "500.", + parsed, + ) + http_response.status_code = 500 + + +def _looks_like_special_case_error(status_code, body): + if status_code == 200 and body: + try: + parser = ETree.XMLParser( + target=ETree.TreeBuilder(), encoding='utf-8' + ) + parser.feed(body) + root = parser.close() + except XMLParseError: + # In cases of network disruptions, we may end up with a partial + # streamed response from S3. We need to treat these cases as + # 500 Service Errors and try again. + return True + if root.tag == 'Error': + return True + return False + + +def set_operation_specific_signer(context, signing_name, **kwargs): + """Choose the operation-specific signer. + + Individual operations may have a different auth type than the service as a + whole. This will most often manifest as operations that should not be + authenticated at all, but can include other auth modes such as sigv4 + without body signing. + """ + auth_type = context.get('auth_type') + + # Auth type will be None if the operation doesn't have a configured auth + # type. + if not auth_type: + return + + # Auth type will be the string value 'none' if the operation should not + # be signed at all. + if auth_type == 'none': + return botocore.UNSIGNED + + if auth_type == 'bearer': + return 'bearer' + + # If the operation needs an unsigned body, we set additional context + # allowing the signer to be aware of this. + if context.get('unsigned_payload') or auth_type == 'v4-unsigned-body': + context['payload_signing_enabled'] = False + + if auth_type.startswith('v4'): + if auth_type == 'v4-s3express': + return auth_type + + if auth_type == 'v4a': + # If sigv4a is chosen, we must add additional signing config for + # global signature. + region = _resolve_sigv4a_region(context) + signing = {'region': region, 'signing_name': signing_name} + if 'signing' in context: + context['signing'].update(signing) + else: + context['signing'] = signing + signature_version = 'v4a' + else: + signature_version = 'v4' + + # Signing names used by s3 and s3-control use customized signers "s3v4" + # and "s3v4a". + if signing_name in S3_SIGNING_NAMES: + signature_version = f's3{signature_version}' + + return signature_version + + +def _resolve_sigv4a_region(context): + region = None + if 'client_config' in context: + region = context['client_config'].sigv4a_signing_region_set + if not region and context.get('signing', {}).get('region'): + region = context['signing']['region'] + return region or '*' + + +def decode_console_output(parsed, **kwargs): + if 'Output' in parsed: + try: + # We're using 'replace' for errors because it is + # possible that console output contains non string + # chars we can't utf-8 decode. + value = base64.b64decode( + bytes(parsed['Output'], 'latin-1') + ).decode('utf-8', 'replace') + parsed['Output'] = value + except (ValueError, TypeError, AttributeError): + logger.debug('Error decoding base64', exc_info=True) + + +def generate_idempotent_uuid(params, model, **kwargs): + for name in model.idempotent_members: + if name not in params: + params[name] = str(uuid.uuid4()) + logger.debug( + f"injecting idempotency token ({params[name]}) into param '{name}'." + ) + + +def decode_quoted_jsondoc(value): + try: + value = json.loads(unquote(value)) + except (ValueError, TypeError): + logger.debug('Error loading quoted JSON', exc_info=True) + return value + + +def json_decode_template_body(parsed, **kwargs): + if 'TemplateBody' in parsed: + try: + value = json.loads( + parsed['TemplateBody'], object_pairs_hook=OrderedDict + ) + parsed['TemplateBody'] = value + except (ValueError, TypeError): + logger.debug('error loading JSON', exc_info=True) + + +def validate_bucket_name(params, **kwargs): + if 'Bucket' not in params: + return + bucket = params['Bucket'] + if not VALID_BUCKET.search(bucket) and not VALID_S3_ARN.search(bucket): + error_msg = ( + f'Invalid bucket name "{bucket}": Bucket name must match ' + f'the regex "{VALID_BUCKET.pattern}" or be an ARN matching ' + f'the regex "{VALID_S3_ARN.pattern}"' + ) + raise ParamValidationError(report=error_msg) + + +def sse_md5(params, **kwargs): + """ + S3 server-side encryption requires the encryption key to be sent to the + server base64 encoded, as well as a base64-encoded MD5 hash of the + encryption key. This handler does both if the MD5 has not been set by + the caller. + """ + _sse_md5(params, 'SSECustomer') + + +def copy_source_sse_md5(params, **kwargs): + """ + S3 server-side encryption requires the encryption key to be sent to the + server base64 encoded, as well as a base64-encoded MD5 hash of the + encryption key. This handler does both if the MD5 has not been set by + the caller specifically if the parameter is for the copy-source sse-c key. + """ + _sse_md5(params, 'CopySourceSSECustomer') + + +def _sse_md5(params, sse_member_prefix='SSECustomer'): + if not _needs_s3_sse_customization(params, sse_member_prefix): + return + + sse_key_member = sse_member_prefix + 'Key' + sse_md5_member = sse_member_prefix + 'KeyMD5' + key_as_bytes = params[sse_key_member] + if isinstance(key_as_bytes, str): + key_as_bytes = key_as_bytes.encode('utf-8') + key_md5_str = base64.b64encode(get_md5(key_as_bytes).digest()).decode( + 'utf-8' + ) + key_b64_encoded = base64.b64encode(key_as_bytes).decode('utf-8') + params[sse_key_member] = key_b64_encoded + params[sse_md5_member] = key_md5_str + + +def _needs_s3_sse_customization(params, sse_member_prefix): + return ( + params.get(sse_member_prefix + 'Key') is not None + and sse_member_prefix + 'KeyMD5' not in params + ) + + +def disable_signing(**kwargs): + """ + This handler disables request signing by setting the signer + name to a special sentinel value. + """ + return botocore.UNSIGNED + + +def add_expect_header(model, params, **kwargs): + if model.http.get('method', '') not in ['PUT', 'POST']: + return + if 'body' in params: + body = params['body'] + if hasattr(body, 'read'): + check_body = utils.ensure_boolean( + os.environ.get( + 'BOTO_EXPERIMENTAL__NO_EMPTY_CONTINUE', + False, + ) + ) + if check_body and utils.determine_content_length(body) == 0: + return + # Any file like object will use an expect 100-continue + # header regardless of size. + logger.debug("Adding expect 100 continue header to request.") + params['headers']['Expect'] = '100-continue' + + +class DeprecatedServiceDocumenter: + def __init__(self, replacement_service_name): + self._replacement_service_name = replacement_service_name + + def inject_deprecation_notice(self, section, event_name, **kwargs): + section.style.start_important() + section.write('This service client is deprecated. Please use ') + section.style.ref( + self._replacement_service_name, + self._replacement_service_name, + ) + section.write(' instead.') + section.style.end_important() + + +def document_copy_source_form(section, event_name, **kwargs): + if 'request-example' in event_name: + parent = section.get_section('structure-value') + param_line = parent.get_section('CopySource') + value_portion = param_line.get_section('member-value') + value_portion.clear_text() + value_portion.write( + "'string' or {'Bucket': 'string', " + "'Key': 'string', 'VersionId': 'string'}" + ) + elif 'request-params' in event_name: + param_section = section.get_section('CopySource') + type_section = param_section.get_section('param-type') + type_section.clear_text() + type_section.write(':type CopySource: str or dict') + doc_section = param_section.get_section('param-documentation') + doc_section.clear_text() + doc_section.write( + "The name of the source bucket, key name of the source object, " + "and optional version ID of the source object. You can either " + "provide this value as a string or a dictionary. The " + "string form is {bucket}/{key} or " + "{bucket}/{key}?versionId={versionId} if you want to copy a " + "specific version. You can also provide this value as a " + "dictionary. The dictionary format is recommended over " + "the string format because it is more explicit. The dictionary " + "format is: {'Bucket': 'bucket', 'Key': 'key', 'VersionId': 'id'}." + " Note that the VersionId key is optional and may be omitted." + " To specify an S3 access point, provide the access point" + " ARN for the ``Bucket`` key in the copy source dictionary. If you" + " want to provide the copy source for an S3 access point as a" + " string instead of a dictionary, the ARN provided must be the" + " full S3 access point object ARN" + " (i.e. {accesspoint_arn}/object/{key})" + ) + + +def handle_copy_source_param(params, **kwargs): + """Convert CopySource param for CopyObject/UploadPartCopy. + + This handler will deal with two cases: + + * CopySource provided as a string. We'll make a best effort + to URL encode the key name as required. This will require + parsing the bucket and version id from the CopySource value + and only encoding the key. + * CopySource provided as a dict. In this case we're + explicitly given the Bucket, Key, and VersionId so we're + able to encode the key and ensure this value is serialized + and correctly sent to S3. + + """ + source = params.get('CopySource') + if source is None: + # The call will eventually fail but we'll let the + # param validator take care of this. It will + # give a better error message. + return + if isinstance(source, str): + params['CopySource'] = _quote_source_header(source) + elif isinstance(source, dict): + params['CopySource'] = _quote_source_header_from_dict(source) + + +def _quote_source_header_from_dict(source_dict): + try: + bucket = source_dict['Bucket'] + key = source_dict['Key'] + version_id = source_dict.get('VersionId') + if VALID_S3_ARN.search(bucket): + final = f'{bucket}/object/{key}' + else: + final = f'{bucket}/{key}' + except KeyError as e: + raise ParamValidationError( + report=f'Missing required parameter: {str(e)}' + ) + final = percent_encode(final, safe=SAFE_CHARS + '/') + if version_id is not None: + final += f'?versionId={version_id}' + return final + + +def _quote_source_header(value): + result = VERSION_ID_SUFFIX.search(value) + if result is None: + return percent_encode(value, safe=SAFE_CHARS + '/') + else: + first, version_id = value[: result.start()], value[result.start() :] + return percent_encode(first, safe=SAFE_CHARS + '/') + version_id + + +def _get_cross_region_presigned_url( + request_signer, request_dict, model, source_region, destination_region +): + # The better way to do this is to actually get the + # endpoint_resolver and get the endpoint_url given the + # source region. In this specific case, we know that + # we can safely replace the dest region with the source + # region because of the supported EC2 regions, but in + # general this is not a safe assumption to make. + # I think eventually we should try to plumb through something + # that allows us to resolve endpoints from regions. + request_dict_copy = copy.deepcopy(request_dict) + request_dict_copy['body']['DestinationRegion'] = destination_region + request_dict_copy['url'] = request_dict['url'].replace( + destination_region, source_region + ) + request_dict_copy['method'] = 'GET' + request_dict_copy['headers'] = {} + return request_signer.generate_presigned_url( + request_dict_copy, region_name=source_region, operation_name=model.name + ) + + +def _get_presigned_url_source_and_destination_regions(request_signer, params): + # Gets the source and destination regions to be used + destination_region = request_signer._region_name + source_region = params.get('SourceRegion') + return source_region, destination_region + + +def inject_presigned_url_ec2(params, request_signer, model, **kwargs): + # The customer can still provide this, so we should pass if they do. + if 'PresignedUrl' in params['body']: + return + src, dest = _get_presigned_url_source_and_destination_regions( + request_signer, params['body'] + ) + url = _get_cross_region_presigned_url( + request_signer, params, model, src, dest + ) + params['body']['PresignedUrl'] = url + # EC2 Requires that the destination region be sent over the wire in + # addition to the source region. + params['body']['DestinationRegion'] = dest + + +def inject_presigned_url_rds(params, request_signer, model, **kwargs): + # SourceRegion is not required for RDS operations, so it's possible that + # it isn't set. In that case it's probably a local copy so we don't need + # to do anything else. + if 'SourceRegion' not in params['body']: + return + + src, dest = _get_presigned_url_source_and_destination_regions( + request_signer, params['body'] + ) + + # Since SourceRegion isn't actually modeled for RDS, it needs to be + # removed from the request params before we send the actual request. + del params['body']['SourceRegion'] + + if 'PreSignedUrl' in params['body']: + return + + url = _get_cross_region_presigned_url( + request_signer, params, model, src, dest + ) + params['body']['PreSignedUrl'] = url + + +def json_decode_policies(parsed, model, **kwargs): + # Any time an IAM operation returns a policy document + # it is a string that is json that has been urlencoded, + # i.e urlencode(json.dumps(policy_document)). + # To give users something more useful, we will urldecode + # this value and json.loads() the result so that they have + # the policy document as a dictionary. + output_shape = model.output_shape + if output_shape is not None: + _decode_policy_types(parsed, model.output_shape) + + +def _decode_policy_types(parsed, shape): + # IAM consistently uses the policyDocumentType shape to indicate + # strings that have policy documents. + shape_name = 'policyDocumentType' + if shape.type_name == 'structure': + for member_name, member_shape in shape.members.items(): + if ( + member_shape.type_name == 'string' + and member_shape.name == shape_name + and member_name in parsed + ): + parsed[member_name] = decode_quoted_jsondoc( + parsed[member_name] + ) + elif member_name in parsed: + _decode_policy_types(parsed[member_name], member_shape) + if shape.type_name == 'list': + shape_member = shape.member + for item in parsed: + _decode_policy_types(item, shape_member) + + +def parse_get_bucket_location(parsed, http_response, **kwargs): + # s3.GetBucketLocation cannot be modeled properly. To + # account for this we just manually parse the XML document. + # The "parsed" passed in only has the ResponseMetadata + # filled out. This handler will fill in the LocationConstraint + # value. + if http_response.raw is None: + return + response_body = http_response.content + parser = ETree.XMLParser(target=ETree.TreeBuilder(), encoding='utf-8') + parser.feed(response_body) + root = parser.close() + region = root.text + parsed['LocationConstraint'] = region + + +def base64_encode_user_data(params, **kwargs): + if 'UserData' in params: + if isinstance(params['UserData'], str): + # Encode it to bytes if it is text. + params['UserData'] = params['UserData'].encode('utf-8') + params['UserData'] = base64.b64encode(params['UserData']).decode( + 'utf-8' + ) + + +def document_base64_encoding(param): + description = ( + '**This value will be base64 encoded automatically. Do ' + 'not base64 encode this value prior to performing the ' + 'operation.**' + ) + append = AppendParamDocumentation(param, description) + return append.append_documentation + + +def validate_ascii_metadata(params, **kwargs): + """Verify S3 Metadata only contains ascii characters. + + From: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html + + "Amazon S3 stores user-defined metadata in lowercase. Each name, value pair + must conform to US-ASCII when using REST and UTF-8 when using SOAP or + browser-based uploads via POST." + + """ + metadata = params.get('Metadata') + if not metadata or not isinstance(metadata, dict): + # We have to at least type check the metadata as a dict type + # because this handler is called before param validation. + # We'll go ahead and return because the param validator will + # give a descriptive error message for us. + # We might need a post-param validation event. + return + for key, value in metadata.items(): + try: + key.encode('ascii') + value.encode('ascii') + except UnicodeEncodeError: + error_msg = ( + 'Non ascii characters found in S3 metadata ' + f'for key "{key}", value: "{value}". \nS3 metadata can only ' + 'contain ASCII characters. ' + ) + raise ParamValidationError(report=error_msg) + + +def fix_route53_ids(params, model, **kwargs): + """ + Check for and split apart Route53 resource IDs, setting + only the last piece. This allows the output of one operation + (e.g. ``'foo/1234'``) to be used as input in another + operation (e.g. it expects just ``'1234'``). + """ + input_shape = model.input_shape + if not input_shape or not hasattr(input_shape, 'members'): + return + + members = [ + name + for (name, shape) in input_shape.members.items() + if shape.name in ['ResourceId', 'DelegationSetId', 'ChangeId'] + ] + + for name in members: + if name in params: + orig_value = params[name] + params[name] = orig_value.split('/')[-1] + logger.debug('%s %s -> %s', name, orig_value, params[name]) + + +def inject_account_id(params, **kwargs): + if params.get('accountId') is None: + # Glacier requires accountId, but allows you + # to specify '-' for the current owners account. + # We add this default value if the user does not + # provide the accountId as a convenience. + params['accountId'] = '-' + + +def add_glacier_version(model, params, **kwargs): + request_dict = params + request_dict['headers']['x-amz-glacier-version'] = model.metadata[ + 'apiVersion' + ] + + +def add_accept_header(model, params, **kwargs): + if params['headers'].get('Accept', None) is None: + request_dict = params + request_dict['headers']['Accept'] = 'application/json' + + +def add_glacier_checksums(params, **kwargs): + """Add glacier checksums to the http request. + + This will add two headers to the http request: + + * x-amz-content-sha256 + * x-amz-sha256-tree-hash + + These values will only be added if they are not present + in the HTTP request. + + """ + request_dict = params + headers = request_dict['headers'] + body = request_dict['body'] + if isinstance(body, bytes): + # If the user provided a bytes type instead of a file + # like object, we're temporarily create a BytesIO object + # so we can use the util functions to calculate the + # checksums which assume file like objects. Note that + # we're not actually changing the body in the request_dict. + body = BytesIO(body) + starting_position = body.tell() + if 'x-amz-content-sha256' not in headers: + headers['x-amz-content-sha256'] = utils.calculate_sha256( + body, as_hex=True + ) + body.seek(starting_position) + if 'x-amz-sha256-tree-hash' not in headers: + headers['x-amz-sha256-tree-hash'] = utils.calculate_tree_hash(body) + body.seek(starting_position) + + +def document_glacier_tree_hash_checksum(): + doc = ''' + This is a required field. + + Ideally you will want to compute this value with checksums from + previous uploaded parts, using the algorithm described in + `Glacier documentation <http://docs.aws.amazon.com/amazonglacier/latest/dev/checksum-calculations.html>`_. + + But if you prefer, you can also use botocore.utils.calculate_tree_hash() + to compute it from raw file by:: + + checksum = calculate_tree_hash(open('your_file.txt', 'rb')) + + ''' + return AppendParamDocumentation('checksum', doc).append_documentation + + +def document_cloudformation_get_template_return_type( + section, event_name, **kwargs +): + if 'response-params' in event_name: + template_body_section = section.get_section('TemplateBody') + type_section = template_body_section.get_section('param-type') + type_section.clear_text() + type_section.write('(*dict*) --') + elif 'response-example' in event_name: + parent = section.get_section('structure-value') + param_line = parent.get_section('TemplateBody') + value_portion = param_line.get_section('member-value') + value_portion.clear_text() + value_portion.write('{}') + + +def switch_host_machinelearning(request, **kwargs): + switch_host_with_param(request, 'PredictEndpoint') + + +def check_openssl_supports_tls_version_1_2(**kwargs): + import ssl + + try: + openssl_version_tuple = ssl.OPENSSL_VERSION_INFO + if openssl_version_tuple < (1, 0, 1): + warnings.warn( + f'Currently installed openssl version: {ssl.OPENSSL_VERSION} does not ' + 'support TLS 1.2, which is required for use of iot-data. ' + 'Please use python installed with openssl version 1.0.1 or ' + 'higher.', + UnsupportedTLSVersionWarning, + ) + # We cannot check the openssl version on python2.6, so we should just + # pass on this conveniency check. + except AttributeError: + pass + + +def change_get_to_post(request, **kwargs): + # This is useful when we need to change a potentially large GET request + # into a POST with x-www-form-urlencoded encoding. + if request.method == 'GET' and '?' in request.url: + request.headers['Content-Type'] = 'application/x-www-form-urlencoded' + request.method = 'POST' + request.url, request.data = request.url.split('?', 1) + + +def set_list_objects_encoding_type_url(params, context, **kwargs): + if 'EncodingType' not in params: + # We set this context so that we know it wasn't the customer that + # requested the encoding. + context['encoding_type_auto_set'] = True + params['EncodingType'] = 'url' + + +def decode_list_object(parsed, context, **kwargs): + # This is needed because we are passing url as the encoding type. Since the + # paginator is based on the key, we need to handle it before it can be + # round tripped. + # + # From the documentation: If you specify encoding-type request parameter, + # Amazon S3 includes this element in the response, and returns encoded key + # name values in the following response elements: + # Delimiter, Marker, Prefix, NextMarker, Key. + _decode_list_object( + top_level_keys=['Delimiter', 'Marker', 'NextMarker'], + nested_keys=[('Contents', 'Key'), ('CommonPrefixes', 'Prefix')], + parsed=parsed, + context=context, + ) + + +def decode_list_object_v2(parsed, context, **kwargs): + # From the documentation: If you specify encoding-type request parameter, + # Amazon S3 includes this element in the response, and returns encoded key + # name values in the following response elements: + # Delimiter, Prefix, ContinuationToken, Key, and StartAfter. + _decode_list_object( + top_level_keys=['Delimiter', 'Prefix', 'StartAfter'], + nested_keys=[('Contents', 'Key'), ('CommonPrefixes', 'Prefix')], + parsed=parsed, + context=context, + ) + + +def decode_list_object_versions(parsed, context, **kwargs): + # From the documentation: If you specify encoding-type request parameter, + # Amazon S3 includes this element in the response, and returns encoded key + # name values in the following response elements: + # KeyMarker, NextKeyMarker, Prefix, Key, and Delimiter. + _decode_list_object( + top_level_keys=[ + 'KeyMarker', + 'NextKeyMarker', + 'Prefix', + 'Delimiter', + ], + nested_keys=[ + ('Versions', 'Key'), + ('DeleteMarkers', 'Key'), + ('CommonPrefixes', 'Prefix'), + ], + parsed=parsed, + context=context, + ) + + +def _decode_list_object(top_level_keys, nested_keys, parsed, context): + if parsed.get('EncodingType') == 'url' and context.get( + 'encoding_type_auto_set' + ): + # URL decode top-level keys in the response if present. + for key in top_level_keys: + if key in parsed: + parsed[key] = unquote_str(parsed[key]) + # URL decode nested keys from the response if present. + for top_key, child_key in nested_keys: + if top_key in parsed: + for member in parsed[top_key]: + member[child_key] = unquote_str(member[child_key]) + + +def convert_body_to_file_like_object(params, **kwargs): + if 'Body' in params: + if isinstance(params['Body'], str): + params['Body'] = BytesIO(ensure_bytes(params['Body'])) + elif isinstance(params['Body'], bytes): + params['Body'] = BytesIO(params['Body']) + + +def _add_parameter_aliases(handler_list): + # Mapping of original parameter to parameter alias. + # The key is <service>.<operation>.parameter + # The first part of the key is used for event registration. + # The last part is the original parameter name and the value is the + # alias to expose in documentation. + aliases = { + 'ec2.*.Filter': 'Filters', + 'logs.CreateExportTask.from': 'fromTime', + 'cloudsearchdomain.Search.return': 'returnFields', + } + + for original, new_name in aliases.items(): + event_portion, original_name = original.rsplit('.', 1) + parameter_alias = ParameterAlias(original_name, new_name) + + # Add the handlers to the list of handlers. + # One handler is to handle when users provide the alias. + # The other handler is to update the documentation to show only + # the alias. + parameter_build_event_handler_tuple = ( + 'before-parameter-build.' + event_portion, + parameter_alias.alias_parameter_in_call, + REGISTER_FIRST, + ) + docs_event_handler_tuple = ( + 'docs.*.' + event_portion + '.complete-section', + parameter_alias.alias_parameter_in_documentation, + ) + handler_list.append(parameter_build_event_handler_tuple) + handler_list.append(docs_event_handler_tuple) + + +class ParameterAlias: + def __init__(self, original_name, alias_name): + self._original_name = original_name + self._alias_name = alias_name + + def alias_parameter_in_call(self, params, model, **kwargs): + if model.input_shape: + # Only consider accepting the alias if it is modeled in the + # input shape. + if self._original_name in model.input_shape.members: + if self._alias_name in params: + if self._original_name in params: + raise AliasConflictParameterError( + original=self._original_name, + alias=self._alias_name, + operation=model.name, + ) + # Remove the alias parameter value and use the old name + # instead. + params[self._original_name] = params.pop(self._alias_name) + + def alias_parameter_in_documentation(self, event_name, section, **kwargs): + if event_name.startswith('docs.request-params'): + if self._original_name not in section.available_sections: + return + # Replace the name for parameter type + param_section = section.get_section(self._original_name) + param_type_section = param_section.get_section('param-type') + self._replace_content(param_type_section) + + # Replace the name for the parameter description + param_name_section = param_section.get_section('param-name') + self._replace_content(param_name_section) + elif event_name.startswith('docs.request-example'): + section = section.get_section('structure-value') + if self._original_name not in section.available_sections: + return + # Replace the name for the example + param_section = section.get_section(self._original_name) + self._replace_content(param_section) + + def _replace_content(self, section): + content = section.getvalue().decode('utf-8') + updated_content = content.replace( + self._original_name, self._alias_name + ) + section.clear_text() + section.write(updated_content) + + +class ClientMethodAlias: + def __init__(self, actual_name): + """Aliases a non-extant method to an existing method. + + :param actual_name: The name of the method that actually exists on + the client. + """ + self._actual = actual_name + + def __call__(self, client, **kwargs): + return getattr(client, self._actual) + + +# TODO: Remove this class as it is no longer used +class HeaderToHostHoister: + """Takes a header and moves it to the front of the hoststring.""" + + _VALID_HOSTNAME = re.compile(r'(?!-)[a-z\d-]{1,63}(?<!-)$', re.IGNORECASE) + + def __init__(self, header_name): + self._header_name = header_name + + def hoist(self, params, **kwargs): + """Hoist a header to the hostname. + + Hoist a header to the beginning of the hostname with a suffix "." after + it. The original header should be removed from the header map. This + method is intended to be used as a target for the before-call event. + """ + if self._header_name not in params['headers']: + return + header_value = params['headers'][self._header_name] + self._ensure_header_is_valid_host(header_value) + original_url = params['url'] + new_url = self._prepend_to_host(original_url, header_value) + params['url'] = new_url + + def _ensure_header_is_valid_host(self, header): + match = self._VALID_HOSTNAME.match(header) + if not match: + raise ParamValidationError( + report=( + 'Hostnames must contain only - and alphanumeric characters, ' + 'and between 1 and 63 characters long.' + ) + ) + + def _prepend_to_host(self, url, prefix): + url_components = urlsplit(url) + parts = url_components.netloc.split('.') + parts = [prefix] + parts + new_netloc = '.'.join(parts) + new_components = ( + url_components.scheme, + new_netloc, + url_components.path, + url_components.query, + '', + ) + new_url = urlunsplit(new_components) + return new_url + + +def inject_api_version_header_if_needed(model, params, **kwargs): + if not model.is_endpoint_discovery_operation: + return + params['headers']['x-amz-api-version'] = model.service_model.api_version + + +def remove_lex_v2_start_conversation(class_attributes, **kwargs): + """Operation requires h2 which is currently unsupported in Python""" + if 'start_conversation' in class_attributes: + del class_attributes['start_conversation'] + + +def remove_qbusiness_chat(class_attributes, **kwargs): + """Operation requires h2 which is currently unsupported in Python""" + if 'chat' in class_attributes: + del class_attributes['chat'] + + +def add_retry_headers(request, **kwargs): + retries_context = request.context.get('retries') + if not retries_context: + return + headers = request.headers + headers['amz-sdk-invocation-id'] = retries_context['invocation-id'] + sdk_retry_keys = ('ttl', 'attempt', 'max') + sdk_request_headers = [ + f'{key}={retries_context[key]}' + for key in sdk_retry_keys + if key in retries_context + ] + headers['amz-sdk-request'] = '; '.join(sdk_request_headers) + + +def remove_bucket_from_url_paths_from_model(params, model, context, **kwargs): + """Strips leading `{Bucket}/` from any operations that have it. + + The original value is retained in a separate "authPath" field. This is + used in the HmacV1Auth signer. See HmacV1Auth.canonical_resource in + botocore/auth.py for details. + + This change is applied to the operation model during the first time the + operation is invoked and then stays in effect for the lifetime of the + client object. + + When the ruleset based endpoint resolver is in effect, both the endpoint + ruleset AND the service model place the bucket name in the final URL. + The result is an invalid URL. This handler modifies the operation model to + no longer place the bucket name. Previous versions of botocore fixed the + URL after the fact when necessary. Since the introduction of ruleset based + endpoint resolution, the problem exists in ALL URLs that contain a bucket + name and can therefore be addressed before the URL gets assembled. + """ + req_uri = model.http['requestUri'] + bucket_path = '/{Bucket}' + if req_uri.startswith(bucket_path): + model.http['requestUri'] = req_uri[len(bucket_path) :] + # Strip query off the requestUri before using as authPath. The + # HmacV1Auth signer will append query params to the authPath during + # signing. + req_uri = req_uri.split('?')[0] + # If the request URI is ONLY a bucket, the auth_path must be + # terminated with a '/' character to generate a signature that the + # server will accept. + needs_slash = req_uri == bucket_path + model.http['authPath'] = f'{req_uri}/' if needs_slash else req_uri + + +def remove_accid_host_prefix_from_model(params, model, context, **kwargs): + """Removes the `{AccountId}.` prefix from the operation model. + + This change is applied to the operation model during the first time the + operation is invoked and then stays in effect for the lifetime of the + client object. + + When the ruleset based endpoint resolver is in effect, both the endpoint + ruleset AND the service model place the {AccountId}. prefix in the URL. + The result is an invalid endpoint. This handler modifies the operation + model to remove the `endpoint.hostPrefix` field while leaving the + `RequiresAccountId` static context parameter in place. + """ + has_ctx_param = any( + ctx_param.name == 'RequiresAccountId' and ctx_param.value is True + for ctx_param in model.static_context_parameters + ) + if ( + model.endpoint is not None + and model.endpoint.get('hostPrefix') == '{AccountId}.' + and has_ctx_param + ): + del model.endpoint['hostPrefix'] + + +def remove_arn_from_signing_path(request, **kwargs): + auth_path = request.auth_path + if isinstance(auth_path, str) and auth_path.startswith('/arn%3A'): + auth_path_parts = auth_path.split('/') + if len(auth_path_parts) > 1 and ArnParser.is_arn( + unquote(auth_path_parts[1]) + ): + request.auth_path = '/'.join(['', *auth_path_parts[2:]]) + + +def customize_endpoint_resolver_builtins( + builtins, model, params, context, **kwargs +): + """Modify builtin parameter values for endpoint resolver + + Modifies the builtins dict in place. Changes are in effect for one call. + The corresponding event is emitted only if at least one builtin parameter + value is required for endpoint resolution for the operation. + """ + bucket_name = params.get('Bucket') + bucket_is_arn = bucket_name is not None and ArnParser.is_arn(bucket_name) + # In some situations the host will return AuthorizationHeaderMalformed + # when the signing region of a sigv4 request is not the bucket's + # region (which is likely unknown by the user of GetBucketLocation). + # Avoid this by always using path-style addressing. + if model.name == 'GetBucketLocation': + builtins[EndpointResolverBuiltins.AWS_S3_FORCE_PATH_STYLE] = True + # All situations where the bucket name is an ARN are not compatible + # with path style addressing. + elif bucket_is_arn: + builtins[EndpointResolverBuiltins.AWS_S3_FORCE_PATH_STYLE] = False + + # Bucket names that are invalid host labels require path-style addressing. + # If path-style addressing was specifically requested, the default builtin + # value is already set. + path_style_required = ( + bucket_name is not None and not VALID_HOST_LABEL_RE.match(bucket_name) + ) + path_style_requested = builtins[ + EndpointResolverBuiltins.AWS_S3_FORCE_PATH_STYLE + ] + + # Path-style addressing is incompatible with the global endpoint for + # presigned URLs. If the bucket name is an ARN, the ARN's region should be + # used in the endpoint. + if ( + context.get('use_global_endpoint') + and not path_style_required + and not path_style_requested + and not bucket_is_arn + and not utils.is_s3express_bucket(bucket_name) + ): + builtins[EndpointResolverBuiltins.AWS_REGION] = 'aws-global' + builtins[EndpointResolverBuiltins.AWS_S3_USE_GLOBAL_ENDPOINT] = True + + +def remove_content_type_header_for_presigning(request, **kwargs): + if ( + request.context.get('is_presign_request') is True + and 'Content-Type' in request.headers + ): + del request.headers['Content-Type'] + + +def handle_expires_header( + operation_model, response_dict, customized_response_dict, **kwargs +): + if _has_expires_shape(operation_model.output_shape): + if expires_value := response_dict.get('headers', {}).get('Expires'): + customized_response_dict['ExpiresString'] = expires_value + try: + utils.parse_timestamp(expires_value) + except (ValueError, RuntimeError): + logger.warning( + f'Failed to parse the "Expires" member as a timestamp: {expires_value}. ' + f'The unparsed value is available in the response under "ExpiresString".' + ) + del response_dict['headers']['Expires'] + + +def _has_expires_shape(shape): + if not shape: + return False + return any( + member_shape.name == 'Expires' + and member_shape.serialization.get('name') == 'Expires' + for member_shape in shape.members.values() + ) + + +def document_expires_shape(section, event_name, **kwargs): + # Updates the documentation for S3 operations that include the 'Expires' member + # in their response structure. Documents a synthetic member 'ExpiresString' and + # includes a deprecation notice for 'Expires'. + if 'response-example' in event_name: + if not section.has_section('structure-value'): + return + parent = section.get_section('structure-value') + if not parent.has_section('Expires'): + return + param_line = parent.get_section('Expires') + param_line.add_new_section('ExpiresString') + new_param_line = param_line.get_section('ExpiresString') + new_param_line.write("'ExpiresString': 'string',") + new_param_line.style.new_line() + elif 'response-params' in event_name: + if not section.has_section('Expires'): + return + param_section = section.get_section('Expires') + # Add a deprecation notice for the "Expires" param + doc_section = param_section.get_section('param-documentation') + doc_section.style.start_note() + doc_section.write( + 'This member has been deprecated. Please use ``ExpiresString`` instead.' + ) + doc_section.style.end_note() + # Document the "ExpiresString" param + new_param_section = param_section.add_new_section('ExpiresString') + new_param_section.style.new_paragraph() + new_param_section.write('- **ExpiresString** *(string) --*') + new_param_section.style.indent() + new_param_section.style.new_paragraph() + new_param_section.write( + 'The raw, unparsed value of the ``Expires`` field.' + ) + + +def _handle_200_error(operation_model, response_dict, **kwargs): + # S3 can return a 200 response with an error embedded in the body. + # Convert the 200 to a 500 for retry resolution in ``_update_status_code``. + if not _should_handle_200_error(operation_model, response_dict): + # Operations with streaming response blobs are excluded as they + # can't be reliably distinguished from an S3 error. + return + if _looks_like_special_case_error( + response_dict['status_code'], response_dict['body'] + ): + response_dict['status_code'] = 500 + logger.debug( + f"Error found for response with 200 status code: {response_dict['body']}." + ) + + +def _should_handle_200_error(operation_model, response_dict): + output_shape = operation_model.output_shape + if ( + not response_dict + or operation_model.has_event_stream_output + or not output_shape + ): + return False + payload = output_shape.serialization.get('payload') + if payload is not None: + payload_shape = output_shape.members[payload] + if payload_shape.type_name in ('blob', 'string'): + return False + return True + + +def _update_status_code(response, **kwargs): + # Update the http_response status code when the parsed response has been + # modified in a handler. This enables retries for cases like ``_handle_200_error``. + if response is None: + return + http_response, parsed = response + parsed_status_code = parsed.get('ResponseMetadata', {}).get( + 'HTTPStatusCode', http_response.status_code + ) + if http_response.status_code != parsed_status_code: + http_response.status_code = parsed_status_code + + +def add_query_compatibility_header(model, params, **kwargs): + if not model.service_model.is_query_compatible: + return + params['headers']['x-amzn-query-mode'] = 'true' + + +def _handle_request_validation_mode_member(params, model, **kwargs): + client_config = kwargs.get("context", {}).get("client_config") + if client_config is None: + return + response_checksum_validation = client_config.response_checksum_validation + http_checksum = model.http_checksum + mode_member = http_checksum.get("requestValidationModeMember") + if ( + mode_member is not None + and response_checksum_validation == "when_supported" + ): + params.setdefault(mode_member, "ENABLED") + + +def _set_extra_headers_for_unsigned_request( + request, signature_version, **kwargs +): + # When sending a checksum in the trailer of an unsigned chunked request, S3 + # requires us to set the "X-Amz-Content-SHA256" header to "STREAMING-UNSIGNED-PAYLOAD-TRAILER". + checksum_context = request.context.get("checksum", {}) + algorithm = checksum_context.get("request_algorithm", {}) + in_trailer = algorithm.get("in") == "trailer" + headers = request.headers + if signature_version == botocore.UNSIGNED and in_trailer: + headers["X-Amz-Content-SHA256"] = "STREAMING-UNSIGNED-PAYLOAD-TRAILER" + + +# This is a list of (event_name, handler). +# When a Session is created, everything in this list will be +# automatically registered with that Session. + +BUILTIN_HANDLERS = [ + ('choose-service-name', handle_service_name_alias), + ( + 'getattr.mturk.list_hi_ts_for_qualification_type', + ClientMethodAlias('list_hits_for_qualification_type'), + ), + ( + 'before-parameter-build.s3.UploadPart', + convert_body_to_file_like_object, + REGISTER_LAST, + ), + ( + 'before-parameter-build.s3.PutObject', + convert_body_to_file_like_object, + REGISTER_LAST, + ), + ('creating-client-class', add_generate_presigned_url), + ('creating-client-class.s3', add_generate_presigned_post), + ('creating-client-class.iot-data', check_openssl_supports_tls_version_1_2), + ('creating-client-class.lex-runtime-v2', remove_lex_v2_start_conversation), + ('creating-client-class.qbusiness', remove_qbusiness_chat), + ('after-call.iam', json_decode_policies), + ('after-call.ec2.GetConsoleOutput', decode_console_output), + ('after-call.cloudformation.GetTemplate', json_decode_template_body), + ('after-call.s3.GetBucketLocation', parse_get_bucket_location), + ('before-parse.s3.*', handle_expires_header), + ('before-parse.s3.*', _handle_200_error, REGISTER_FIRST), + ('before-parameter-build', generate_idempotent_uuid), + ('before-parameter-build', _handle_request_validation_mode_member), + ('before-parameter-build.s3', validate_bucket_name), + ('before-parameter-build.s3', remove_bucket_from_url_paths_from_model), + ( + 'before-parameter-build.s3.ListObjects', + set_list_objects_encoding_type_url, + ), + ( + 'before-parameter-build.s3.ListObjectsV2', + set_list_objects_encoding_type_url, + ), + ( + 'before-parameter-build.s3.ListObjectVersions', + set_list_objects_encoding_type_url, + ), + ('before-parameter-build.s3.CopyObject', handle_copy_source_param), + ('before-parameter-build.s3.UploadPartCopy', handle_copy_source_param), + ('before-parameter-build.s3.CopyObject', validate_ascii_metadata), + ('before-parameter-build.s3.PutObject', validate_ascii_metadata), + ( + 'before-parameter-build.s3.CreateMultipartUpload', + validate_ascii_metadata, + ), + ('before-parameter-build.s3-control', remove_accid_host_prefix_from_model), + ('docs.*.s3.CopyObject.complete-section', document_copy_source_form), + ('docs.*.s3.UploadPartCopy.complete-section', document_copy_source_form), + ('docs.response-example.s3.*.complete-section', document_expires_shape), + ('docs.response-params.s3.*.complete-section', document_expires_shape), + ('before-endpoint-resolution.s3', customize_endpoint_resolver_builtins), + ('before-call', add_recursion_detection_header), + ('before-call', add_query_compatibility_header), + ('before-call.s3', add_expect_header), + ('before-call.glacier', add_glacier_version), + ('before-call.apigateway', add_accept_header), + ('before-call.s3.DeleteObjects', escape_xml_payload), + ('before-call.s3.PutBucketLifecycleConfiguration', escape_xml_payload), + ('before-call.glacier.UploadArchive', add_glacier_checksums), + ('before-call.glacier.UploadMultipartPart', add_glacier_checksums), + ('before-call.ec2.CopySnapshot', inject_presigned_url_ec2), + ('request-created', add_retry_headers), + ('request-created.machinelearning.Predict', switch_host_machinelearning), + ('needs-retry.s3.*', _update_status_code, REGISTER_FIRST), + ('choose-signer.cognito-identity.GetId', disable_signing), + ('choose-signer.cognito-identity.GetOpenIdToken', disable_signing), + ('choose-signer.cognito-identity.UnlinkIdentity', disable_signing), + ( + 'choose-signer.cognito-identity.GetCredentialsForIdentity', + disable_signing, + ), + ('choose-signer.sts.AssumeRoleWithSAML', disable_signing), + ('choose-signer.sts.AssumeRoleWithWebIdentity', disable_signing), + ('choose-signer', set_operation_specific_signer), + ('before-parameter-build.s3.HeadObject', sse_md5), + ('before-parameter-build.s3.GetObject', sse_md5), + ('before-parameter-build.s3.PutObject', sse_md5), + ('before-parameter-build.s3.CopyObject', sse_md5), + ('before-parameter-build.s3.CopyObject', copy_source_sse_md5), + ('before-parameter-build.s3.CreateMultipartUpload', sse_md5), + ('before-parameter-build.s3.UploadPart', sse_md5), + ('before-parameter-build.s3.UploadPartCopy', sse_md5), + ('before-parameter-build.s3.UploadPartCopy', copy_source_sse_md5), + ('before-parameter-build.s3.CompleteMultipartUpload', sse_md5), + ('before-parameter-build.s3.SelectObjectContent', sse_md5), + ('before-parameter-build.ec2.RunInstances', base64_encode_user_data), + ( + 'before-parameter-build.autoscaling.CreateLaunchConfiguration', + base64_encode_user_data, + ), + ('before-parameter-build.route53', fix_route53_ids), + ('before-parameter-build.glacier', inject_account_id), + ('before-sign.s3', remove_arn_from_signing_path), + ('before-sign.s3', _set_extra_headers_for_unsigned_request), + ( + 'before-sign.polly.SynthesizeSpeech', + remove_content_type_header_for_presigning, + ), + ('after-call.s3.ListObjects', decode_list_object), + ('after-call.s3.ListObjectsV2', decode_list_object_v2), + ('after-call.s3.ListObjectVersions', decode_list_object_versions), + # Cloudsearchdomain search operation will be sent by HTTP POST + ('request-created.cloudsearchdomain.Search', change_get_to_post), + # Glacier documentation customizations + ( + 'docs.*.glacier.*.complete-section', + AutoPopulatedParam( + 'accountId', + 'Note: this parameter is set to "-" by' + 'default if no value is not specified.', + ).document_auto_populated_param, + ), + ( + 'docs.*.glacier.UploadArchive.complete-section', + AutoPopulatedParam('checksum').document_auto_populated_param, + ), + ( + 'docs.*.glacier.UploadMultipartPart.complete-section', + AutoPopulatedParam('checksum').document_auto_populated_param, + ), + ( + 'docs.request-params.glacier.CompleteMultipartUpload.complete-section', + document_glacier_tree_hash_checksum(), + ), + # Cloudformation documentation customizations + ( + 'docs.*.cloudformation.GetTemplate.complete-section', + document_cloudformation_get_template_return_type, + ), + # UserData base64 encoding documentation customizations + ( + 'docs.*.ec2.RunInstances.complete-section', + document_base64_encoding('UserData'), + ), + ( + 'docs.*.autoscaling.CreateLaunchConfiguration.complete-section', + document_base64_encoding('UserData'), + ), + # EC2 CopySnapshot documentation customizations + ( + 'docs.*.ec2.CopySnapshot.complete-section', + AutoPopulatedParam('PresignedUrl').document_auto_populated_param, + ), + ( + 'docs.*.ec2.CopySnapshot.complete-section', + AutoPopulatedParam('DestinationRegion').document_auto_populated_param, + ), + # S3 SSE documentation modifications + ( + 'docs.*.s3.*.complete-section', + AutoPopulatedParam('SSECustomerKeyMD5').document_auto_populated_param, + ), + # S3 SSE Copy Source documentation modifications + ( + 'docs.*.s3.*.complete-section', + AutoPopulatedParam( + 'CopySourceSSECustomerKeyMD5' + ).document_auto_populated_param, + ), + # Add base64 information to Lambda + ( + 'docs.*.lambda.UpdateFunctionCode.complete-section', + document_base64_encoding('ZipFile'), + ), + # The following S3 operations cannot actually accept a ContentMD5 + ( + 'docs.*.s3.*.complete-section', + HideParamFromOperations( + 's3', + 'ContentMD5', + [ + 'DeleteObjects', + 'PutBucketAcl', + 'PutBucketCors', + 'PutBucketLifecycle', + 'PutBucketLogging', + 'PutBucketNotification', + 'PutBucketPolicy', + 'PutBucketReplication', + 'PutBucketRequestPayment', + 'PutBucketTagging', + 'PutBucketVersioning', + 'PutBucketWebsite', + 'PutObjectAcl', + ], + ).hide_param, + ), + ############# + # DSQL + ############# + ('creating-client-class.dsql', add_dsql_generate_db_auth_token_methods), + ############# + # RDS + ############# + ('creating-client-class.rds', add_generate_db_auth_token), + ('before-call.rds.CopyDBClusterSnapshot', inject_presigned_url_rds), + ('before-call.rds.CreateDBCluster', inject_presigned_url_rds), + ('before-call.rds.CopyDBSnapshot', inject_presigned_url_rds), + ('before-call.rds.CreateDBInstanceReadReplica', inject_presigned_url_rds), + ( + 'before-call.rds.StartDBInstanceAutomatedBackupsReplication', + inject_presigned_url_rds, + ), + # RDS PresignedUrl documentation customizations + ( + 'docs.*.rds.CopyDBClusterSnapshot.complete-section', + AutoPopulatedParam('PreSignedUrl').document_auto_populated_param, + ), + ( + 'docs.*.rds.CreateDBCluster.complete-section', + AutoPopulatedParam('PreSignedUrl').document_auto_populated_param, + ), + ( + 'docs.*.rds.CopyDBSnapshot.complete-section', + AutoPopulatedParam('PreSignedUrl').document_auto_populated_param, + ), + ( + 'docs.*.rds.CreateDBInstanceReadReplica.complete-section', + AutoPopulatedParam('PreSignedUrl').document_auto_populated_param, + ), + ( + 'docs.*.rds.StartDBInstanceAutomatedBackupsReplication.complete-section', + AutoPopulatedParam('PreSignedUrl').document_auto_populated_param, + ), + ############# + # Neptune + ############# + ('before-call.neptune.CopyDBClusterSnapshot', inject_presigned_url_rds), + ('before-call.neptune.CreateDBCluster', inject_presigned_url_rds), + # Neptune PresignedUrl documentation customizations + ( + 'docs.*.neptune.CopyDBClusterSnapshot.complete-section', + AutoPopulatedParam('PreSignedUrl').document_auto_populated_param, + ), + ( + 'docs.*.neptune.CreateDBCluster.complete-section', + AutoPopulatedParam('PreSignedUrl').document_auto_populated_param, + ), + ############# + # DocDB + ############# + ('before-call.docdb.CopyDBClusterSnapshot', inject_presigned_url_rds), + ('before-call.docdb.CreateDBCluster', inject_presigned_url_rds), + # DocDB PresignedUrl documentation customizations + ( + 'docs.*.docdb.CopyDBClusterSnapshot.complete-section', + AutoPopulatedParam('PreSignedUrl').document_auto_populated_param, + ), + ( + 'docs.*.docdb.CreateDBCluster.complete-section', + AutoPopulatedParam('PreSignedUrl').document_auto_populated_param, + ), + ('before-call', inject_api_version_header_if_needed), +] +_add_parameter_aliases(BUILTIN_HANDLERS) |