### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

CopyObject API does not check precondition headers such as `x-amz-copy-source-if-modified-since`. Instead it seems to always copy the object.

### Expected Behavior

The API should return a 412 response with content similar to:

```
<Error><Code>PreconditionFailed</Code><Message>At least one of the pre-conditions you specified did not hold</Message><Condition>x-amz-copy-source-If-Modified-Since</Condition><RequestId>14HR9PXJRG5B1GR5</RequestId><HostId>HztBs2jYea1fzzPWo1JWy33NQJqo1S1IsK62rkbnVEI04PnGNCi8sPVrichGQxH3FQwLA2RIyF4=</HostId></Error>
```

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

    docker run localstack/localstack

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

```
aws s3  --profile localstack --endpoint-url http://s3.localhost.localstack.cloud:4566 \
  ls s3://net-amazon-s3-test/Makefile.am
2023-01-25 10:47:40        537 Makefile.am

aws s3api copy-object --copy-source net-amazon-s3-test/Makefile.am --key Makefile.am.2 \
  --bucket net-amazon-s3-test2 --profile localstack --endpoint-url http://s3.localhost.localstack.cloud:4566 \
  --copy-source-if-modified-since "Wed, 25 Jan 2023 16:16:27 GMT"
{
    "CopyObjectResult": {
        "ETag": "\"46ada9d1fe8f8a311ff4504c226e061c\"",
        "LastModified": "2023-01-25T16:18:43+00:00"
    }
}
```

### Environment

```markdown
- OS: penguin
- LocalStack: latest
```


### Anything else?

_No response_
"""
    This class will take a source stream, and return only a range of it based on the parameters start_byte and end_byte
    """
    def __init__(self, base_stream: IO[bytes], start_byte: int, end_byte: int):
        super().__init__()
        self._base_stream = base_stream
        self._pos = start_byte
        self._length = end_byte - start_byte + 1
    def read(self, s: int = -1) -> bytes | None:
        self._base_stream.seek(self._pos)
        if not s or s < 0:
            amount = self._length
        else:
            amount = min(self._length, s)
        data = self._base_stream.read(amount)
        if not data:
            return b""
        read_amount = len(data)
        self._length -= read_amount
        self._pos += read_amount
        return data
class StreamedFakeKey(s3_models.FakeKey):
    """
    We are overriding the `FakeKey` object from moto to allow streaming input and output instead of loading its full
    value into memory. Most of the changes are related to checksum validation, as we would pass the entire value to the
    checksum before, and we now do it in a chunked manner.
    """
    def __init__(self, name: str, value: IO[bytes], *args, **kwargs):