Repository: localstack/localstack

Issue:
bug: SQS client crashes if malformed request
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

When sending a "malformed" request (and empty string in a MessageAttribute) the SQS client crashes with:
```
SerializationError: failed to unmarshal error message
	status code: 500, request id: 
caused by: UnmarshalError: failed to unmarshal error message
	00000000  3c 21 64 6f 63 74 79 70  65 20 68 74 6d 6c 3e 0a  |<!doctype html>.|
00000010  3c 68 74 6d 6c 20 6c 61  6e 67 3d 65 6e 3e 0a 3c  |<html lang=en>.<|
00000020  74 69 74 6c 65 3e 35 30  30 20 49 6e 74 65 72 6e  |title>500 Intern|
00000030  61 6c 20 53 65 72 76 65  72 20 45 72 72 6f 72 3c  |al Server Error<|
00000040  2f 74 69 74 6c 65 3e 0a  3c 68 31 3e 49 6e 74 65  |/title>.<h1>Inte|
00000050  72 6e 61 6c 20 53 65 72  76 65 72 20 45 72 72 6f  |rnal Server Erro|
00000060  72 3c 2f 68 31 3e 0a 3c  70 3e 54 68 65 20 73 65  |r</h1>.<p>The se|
00000070  72 76 65 72 20 65 6e 63  6f 75 6e 74 65 72 65 64  |rver encountered|
00000080  20 61 6e 20 69 6e 74 65  72 6e 61 6c 20 65 72 72  | an internal err|
00000090  6f 72 20 61 6e 64 20 77  61 73 20 75 6e 61 62 6c  |or and was unabl|
000000a0  65 20 74 6f 20 63 6f 6d  70 6c 65 74 65 20 79 6f  |e to complete yo|
000000b0  75 72 20 72 65 71 75 65  73 74 2e 20 45 69 74 68  |ur request. Eith|
000000c0  65 72 20 74 68 65 20 73  65 72 76 65 72 20 69 73  |er the server is|
000000d0  20 6f 76 65 72 6c 6f 61  64 65 64 20 6f 72 20 74  | overloaded or t|
000000e0  68 65 72 65 20 69 73 20  61 6e 20 65 72 72 6f 72  |here is an error|
000000f0  20 69 6e 20 74 68 65 20  61 70 70 6c 69 63 61 74  | in the applicat|
00000100  69 6f 6e 2e 3c 2f 70 3e  0a                       |ion.</p>.|

caused by: XML syntax error on line 2: unquoted or missing attribute value in element
```

### Expected Behavior

When (trying to) sending the same message on a "real" AWS SQS queue, this is the error message:

```
InvalidParameterValue: Message (user) attribute 'ErrorDetails' must contain a non-empty value of type 'String'.
	status code: 400, request id: db2719d5-7bd3-5aa0-af81-43203661447a
```

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

Dockercompose.yaml (snippet)
```

  localstack:
    container_name: "aws-sqs"
    image: "localstack/localstack:latest"
    hostname: aws-sqs
    environment:
      - AWS_REGION=us-west-2
      - EDGE_PORT=4566
      - SERVICES=sqs
    ports:
      - '4566:4566'
    volumes:
      - "${TMPDIR:-/tmp}/localstack:/tmp/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
    networks:
      - backend
```

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

      aws --endpoint-url=http://localhost:4566 --region us-west-2 sqs create-queue --queue-name events



### Environment

```markdown
- OS: Ubuntu 22.04
- LocalStack: localstack/localstack:latest 
- Go: go version go1.18 linux/amd64
```


### Anything else?

[Go Statemachine](https://github.com/massenz/go-statemachine)

Currently in a dev branch: `statemachine-sqs`


Discussion:
Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our [Slack Community](https://localstack.cloud/contact/) for real-time help and drop a message to LocalStack Pro Support if you are a Pro user! If you are willing to contribute towards fixing this issue, please have a look at our [contributing guidelines](https://github.com/localstack/.github/blob/main/CONTRIBUTING.md) and our [developer guide](https://docs.localstack.cloud/developer-guide/).
BTW - totally appreciate the crash and "marshal error" is most likely in the AWS Go SDK, and I'll report it there too; but wanted you guys to be aware that you're sending back something that the client doesn't understand and is materially different from what SQS sends back (which doesn't exercise the buggy code path)

Resolving patch:
diff --git a/localstack/services/sqs/provider.py b/localstack/services/sqs/provider.py
--- a/localstack/services/sqs/provider.py
+++ b/localstack/services/sqs/provider.py
@@ -96,7 +96,7 @@ def generate_receipt_handle():
 
 class InvalidParameterValue(CommonServiceException):
     def __init__(self, message):
-        super().__init__("InvalidParameterValues", message, 400, True)
+        super().__init__("InvalidParameterValue", message, 400, True)
 
 
 class InvalidAttributeValue(CommonServiceException):
@@ -638,6 +638,12 @@ def check_attributes(message_attributes: MessageBodyAttributeMap):
         if attribute_type == "String":
             try:
                 attribute_value = attribute.get("StringValue")
+
+                if not attribute_value:
+                    raise InvalidParameterValue(
+                        f"Message (user) attribute '{attribute_name}' must contain a non-empty value of type 'String'."
+                    )
+
                 check_message_content(attribute_value)
             except InvalidMessageContents as e:
                 # AWS throws a different exception here


Test patch:
diff --git a/tests/integration/test_sqs.py b/tests/integration/test_sqs.py
--- a/tests/integration/test_sqs.py
+++ b/tests/integration/test_sqs.py
@@ -730,13 +730,7 @@ def test_create_and_send_to_fifo_queue(self, sqs_client, sqs_create_queue):
         result_recv = sqs_client.receive_message(QueueUrl=queue_url)
         assert result_recv["Messages"][0]["MessageId"] == message_id
 
-    def test_fifo_queue_without_fifo_queue_attribute(self, sqs_create_queue):
-        queue_name = f"invalid-{short_uid()}.fifo"
-
-        with pytest.raises(Exception) as e:
-            sqs_create_queue(QueueName=queue_name)
-        e.match("InvalidParameterValue")
-
+    @pytest.mark.aws_validated
     def test_fifo_queue_requires_suffix(self, sqs_create_queue):
         queue_name = f"invalid-{short_uid()}"
         attributes = {"FifoQueue": "true"}
@@ -745,6 +739,7 @@ def test_fifo_queue_requires_suffix(self, sqs_create_queue):
             sqs_create_queue(QueueName=queue_name, Attributes=attributes)
         e.match("InvalidParameterValue")
 
+    @pytest.mark.aws_validated
     def test_standard_queue_cannot_have_fifo_suffix(self, sqs_create_queue):
         queue_name = f"queue-{short_uid()}.fifo"
         with pytest.raises(Exception) as e:
@@ -902,7 +897,22 @@ def test_sent_message_retains_attributes_after_receive(self, sqs_client, sqs_cre
         )
         assert receive_result["Messages"][0]["MessageAttributes"] == attributes
 
-    @pytest.mark.xfail
+    @pytest.mark.aws_validated
+    def test_send_message_with_empty_string_attribute(self, sqs_client, sqs_queue):
+        with pytest.raises(ClientError) as e:
+            sqs_client.send_message(
+                QueueUrl=sqs_queue,
+                MessageBody="test",
+                MessageAttributes={"ErrorDetails": {"StringValue": "", "DataType": "String"}},
+            )
+
+        assert e.value.response["Error"] == {
+            "Type": "Sender",
+            "Code": "InvalidParameterValue",
+            "Message": "Message (user) attribute 'ErrorDetails' must contain a non-empty value of type 'String'.",
+        }
+
+    @pytest.mark.aws_validated
     def test_send_message_with_invalid_string_attributes(self, sqs_client, sqs_create_queue):
         queue_name = f"queue-{short_uid()}"
         queue_url = sqs_create_queue(QueueName=queue_name)