Example #1
0
    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
Example #2
0
    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
Example #3
0
    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)
Example #4
0
    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
Example #5
0
    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)