def forward_request(self, method, path, data, headers): if method == 'OPTIONS': return 200 req_data = parse_request_data(method, path, data) if is_sqs_queue_url(path) and method == 'GET': if not headers.get('Authorization'): headers['Authorization'] = aws_stack.mock_aws_request_headers(service='sqs')['Authorization'] method = 'POST' req_data = {'Action': 'GetQueueUrl', 'Version': API_VERSION, 'QueueName': path.split('/')[-1]} if req_data: action = req_data.get('Action') if action in ('SendMessage', 'SendMessageBatch') and SQS_BACKEND_IMPL == 'moto': # check message contents for key, value in req_data.items(): if not re.match(MSG_CONTENT_REGEX, str(value)): return make_requests_error(code=400, code_string='InvalidMessageContents', message='Message contains invalid characters') elif action == 'SetQueueAttributes': # TODO remove this function if we stop using ElasticMQ entirely queue_url = _queue_url(path, req_data, headers) if SQS_BACKEND_IMPL == 'elasticmq': forward_attrs = _set_queue_attributes(queue_url, req_data) if len(req_data) != len(forward_attrs): # make sure we only forward the supported attributes to the backend return _get_attributes_forward_request(method, path, headers, req_data, forward_attrs) elif action == 'CreateQueue': changed_attrs = _fix_dlq_arn_in_attributes(req_data) if changed_attrs: return _get_attributes_forward_request(method, path, headers, req_data, changed_attrs) elif action == 'DeleteQueue': queue_url = _queue_url(path, req_data, headers) QUEUE_ATTRIBUTES.pop(queue_url, None) sns_listener.unsubscribe_sqs_queue(queue_url) elif action == 'ListDeadLetterSourceQueues': # TODO remove this function if we stop using ElasticMQ entirely queue_url = _queue_url(path, req_data, headers) if SQS_BACKEND_IMPL == 'elasticmq': headers = {'content-type': 'application/xhtml+xml'} content_str = _list_dead_letter_source_queues(QUEUE_ATTRIBUTES, queue_url) return requests_response(content_str, headers=headers) if 'QueueName' in req_data: encoded_data = urlencode(req_data, doseq=True) if method == 'POST' else '' modified_url = None if method == 'GET': base_path = path.partition('?')[0] modified_url = '%s?%s' % (base_path, urlencode(req_data, doseq=True)) return Request(data=encoded_data, url=modified_url, headers=headers, method=method) return True
def forward_request(self, method, path, data, headers): if method == 'OPTIONS': return 200 req_data = parse_request_data(method, path, data) if req_data: action = req_data.get('Action') if action in ('SendMessage', 'SendMessageBatch') and BACKEND_IMPL == 'moto': # check message contents for key, value in req_data.items(): if not re.match(MSG_CONTENT_REGEX, str(value)): return make_requests_error( code=400, code_string='InvalidMessageContents', message='Message contains invalid characters') elif action == 'SetQueueAttributes': queue_url = _queue_url(path, req_data, headers) forward_attrs = _set_queue_attributes(queue_url, req_data) if len(req_data) != len(forward_attrs): # make sure we only forward the supported attributes to the backend return _get_attributes_forward_request( method, path, headers, req_data, forward_attrs) elif action == 'DeleteQueue': queue_url = _queue_url(path, req_data, headers) QUEUE_ATTRIBUTES.pop(queue_url, None) sns_listener.unsubscribe_sqs_queue(queue_url) elif action == 'ListDeadLetterSourceQueues': queue_url = _queue_url(path, req_data, headers) headers = {'content-type': 'application/xhtml+xml'} content_str = _list_dead_letter_source_queues( QUEUE_ATTRIBUTES, queue_url) return requests_response(content_str, headers=headers) if 'QueueName' in req_data: encoded_data = urlencode( req_data, doseq=True) if method == 'POST' else '' modified_url = None if method == 'GET': base_path = path.partition('?')[0] modified_url = '%s?%s' % (base_path, urlencode(req_data, doseq=True)) return Request(data=encoded_data, url=modified_url, headers=headers, method=method) return True
def return_response(self, method, path, data, headers, response): # persist requests to disk super(ProxyListenerSQS, self).return_response(method, path, data, headers, response) if method == "OPTIONS" and path == "/": # Allow CORS preflight requests to succeed. return 200 if method != "POST": return region_name = aws_stack.get_region() req_data = parse_request_data(method, path, data) action = req_data.get("Action") content_str = content_str_original = to_str(response.content) if response.status_code >= 400: return response _fire_event(req_data, response) # patch the response and add missing attributes if action == "GetQueueAttributes": content_str = _add_queue_attributes(path, req_data, content_str, headers) name = r"<Name>\s*RedrivePolicy\s*<\/Name>" value = r"<Value>\s*{(.*)}\s*<\/Value>" for p1, p2 in ((name, value), (value, name)): content_str = re.sub( r"<Attribute>\s*%s\s*%s\s*<\/Attribute>" % (p1, p2), _fix_redrive_policy, content_str, ) # patch the response and return the correct endpoint URLs / ARNs if action in ( "CreateQueue", "GetQueueUrl", "ListQueues", "GetQueueAttributes", "ListDeadLetterSourceQueues", ): if config.USE_SSL and "<QueueUrl>http://" in content_str: # return https://... if we're supposed to use SSL content_str = re.sub(r"<QueueUrl>\s*http://", r"<QueueUrl>https://", content_str) # expose external hostname:port external_port = SQS_PORT_EXTERNAL or get_external_port(headers) content_str = re.sub( r"<QueueUrl>\s*([a-z]+)://[^<]*:([0-9]+)/([^<]*)\s*</QueueUrl>", r"<QueueUrl>\1://%s:%s/\3</QueueUrl>" % (config.HOSTNAME_EXTERNAL, external_port), content_str, ) # encode account ID in queue URL content_str = re.sub( r"<QueueUrl>\s*([a-z]+)://([^/]+)/queue/([^<]*)\s*</QueueUrl>", r"<QueueUrl>\1://\2/%s/\3</QueueUrl>" % constants.TEST_AWS_ACCOUNT_ID, content_str, ) # fix queue ARN content_str = re.sub( r"<([a-zA-Z0-9]+)>\s*arn:aws:sqs:elasticmq:([^<]+)</([a-zA-Z0-9]+)>", r"<\1>arn:aws:sqs:%s:\2</\3>" % region_name, content_str, ) if action == "CreateQueue": regex = r".*<QueueUrl>(.*)</QueueUrl>" queue_url = re.match(regex, content_str, re.DOTALL).group(1) if SQS_BACKEND_IMPL == "elasticmq": _set_queue_attributes(queue_url, req_data) elif action == "SendMessageBatch": if validate_empty_message_batch(data, req_data): msg = "There should be at least one SendMessageBatchRequestEntry in the request." return make_requests_error(code=404, code_string="EmptyBatchRequest", message=msg) # instruct listeners to process new SQS message if action in ("SendMessage", "SendMessageBatch"): _process_sent_message(path, req_data, headers, response) if content_str_original != content_str: # if changes have been made, return patched response response.headers["Content-Length"] = len(content_str) response.headers["x-amz-crc32"] = calculate_crc32(content_str) return requests_response(content_str, headers=response.headers, status_code=response.status_code)
def forward_request(self, method, path, data, headers): if method == "OPTIONS": return 200 req_data = parse_request_data(method, path, data) if is_sqs_queue_url(path) and method == "GET": if not headers.get("Authorization"): headers["Authorization"] = aws_stack.mock_aws_request_headers( service="sqs")["Authorization"] method = "POST" req_data = { "Action": "GetQueueUrl", "Version": API_VERSION, "QueueName": path.split("/")[-1], } if req_data: action = req_data.get("Action") if action in ("SendMessage", "SendMessageBatch") and SQS_BACKEND_IMPL == "moto": # check message contents for key, value in req_data.items(): if not re.match(MSG_CONTENT_REGEX, str(value)): return make_requests_error( code=400, code_string="InvalidMessageContents", message="Message contains invalid characters", ) elif action == "SetQueueAttributes": # TODO remove this function if we stop using ElasticMQ queue_url = _queue_url(path, req_data, headers) if SQS_BACKEND_IMPL == "elasticmq": forward_attrs = _set_queue_attributes(queue_url, req_data) if len(req_data) != len(forward_attrs): # make sure we only forward the supported attributes to the backend return _get_attributes_forward_request( method, path, headers, req_data, forward_attrs) elif action == "TagQueue": req_data = self.fix_missing_tag_values(req_data) elif action == "CreateQueue": req_data = self.fix_missing_tag_values(req_data) def _is_fifo(): for k, v in req_data.items(): if v == "FifoQueue": return req_data[k.replace( "Name", "Value")].lower() == "true" return False if req_data.get("QueueName").endswith( ".fifo") and not _is_fifo(): msg = "Can only include alphanumeric characters, hyphens, or underscores. 1 to 80 in length" return make_requests_error( code=400, code_string="InvalidParameterValue", message=msg) changed_attrs = _fix_dlq_arn_in_attributes(req_data) if changed_attrs: return _get_attributes_forward_request( method, path, headers, req_data, changed_attrs) elif action == "DeleteQueue": queue_url = _queue_url(path, req_data, headers) QUEUE_ATTRIBUTES.pop(queue_url, None) sns_listener.unsubscribe_sqs_queue(queue_url) elif action == "ListDeadLetterSourceQueues": # TODO remove this function if we stop using ElasticMQ entirely queue_url = _queue_url(path, req_data, headers) if SQS_BACKEND_IMPL == "elasticmq": headers = {"content-type": "application/xhtml+xml"} content_str = _list_dead_letter_source_queues( QUEUE_ATTRIBUTES, queue_url) return requests_response(content_str, headers=headers) if "QueueName" in req_data: encoded_data = urlencode( req_data, doseq=True) if method == "POST" else "" modified_url = None if method == "GET": base_path = path.partition("?")[0] modified_url = "%s?%s" % ( base_path, urlencode(req_data, doseq=True), ) return Request(data=encoded_data, url=modified_url, headers=headers, method=method) return True
def return_response(self, method, path, data, headers, response, request_handler): # persist requests to disk super(ProxyListenerSQS, self).return_response(method, path, data, headers, response, request_handler) if method == 'OPTIONS' and path == '/': # Allow CORS preflight requests to succeed. return 200 if method != 'POST': return region_name = aws_stack.get_region() req_data = parse_request_data(method, path, data) action = req_data.get('Action') content_str = content_str_original = to_str(response.content) if response.status_code >= 400: return response _fire_event(req_data, response) # patch the response and add missing attributes if action == 'GetQueueAttributes': content_str = _add_queue_attributes(path, req_data, content_str, headers) name = r'<Name>\s*RedrivePolicy\s*<\/Name>' value = r'<Value>\s*{(.*)}\s*<\/Value>' for p1, p2 in ((name, value), (value, name)): content_str = re.sub( r'<Attribute>\s*%s\s*%s\s*<\/Attribute>' % (p1, p2), _fix_redrive_policy, content_str) # patch the response and return the correct endpoint URLs / ARNs if action in ('CreateQueue', 'GetQueueUrl', 'ListQueues', 'GetQueueAttributes', 'ListDeadLetterSourceQueues'): if config.USE_SSL and '<QueueUrl>http://' in content_str: # return https://... if we're supposed to use SSL content_str = re.sub(r'<QueueUrl>\s*http://', r'<QueueUrl>https://', content_str) # expose external hostname:port external_port = SQS_PORT_EXTERNAL or get_external_port( headers, request_handler) content_str = re.sub( r'<QueueUrl>\s*([a-z]+)://[^<]*:([0-9]+)/([^<]*)\s*</QueueUrl>', r'<QueueUrl>\1://%s:%s/\3</QueueUrl>' % (HOSTNAME_EXTERNAL, external_port), content_str) # encode account ID in queue URL content_str = re.sub( r'<QueueUrl>\s*([a-z]+)://([^/]+)/queue/([^<]*)\s*</QueueUrl>', r'<QueueUrl>\1://\2/%s/\3</QueueUrl>' % constants.TEST_AWS_ACCOUNT_ID, content_str) # fix queue ARN content_str = re.sub( r'<([a-zA-Z0-9]+)>\s*arn:aws:sqs:elasticmq:([^<]+)</([a-zA-Z0-9]+)>', r'<\1>arn:aws:sqs:%s:\2</\3>' % region_name, content_str) if action == 'CreateQueue': queue_url = re.match(r'.*<QueueUrl>(.*)</QueueUrl>', content_str, re.DOTALL).group(1) if SQS_BACKEND_IMPL == 'elasticmq': _set_queue_attributes(queue_url, req_data) elif action == 'SendMessageBatch': if validate_empty_message_batch(data, req_data): msg = 'There should be at least one SendMessageBatchRequestEntry in the request.' return make_requests_error(code=404, code_string='EmptyBatchRequest', message=msg) # instruct listeners to fetch new SQS message if action in ('SendMessage', 'SendMessageBatch'): _process_sent_message(path, req_data, headers) if content_str_original != content_str: # if changes have been made, return patched response response.headers['content-length'] = len(content_str) response.headers['x-amz-crc32'] = calculate_crc32(content_str) return requests_response(content_str, headers=response.headers, status_code=response.status_code)