Repository: getmoto/moto

Issue:
exception when requesting a s3.Object.get() with Range=""
**Steps to reproduce:**
```python
from moto import mock_s3
import boto3

file_path = "..."
with mock_s3():
    bucket = "test"
    object_key = "key"
    s3 = boto3.resource("s3")
    s3.create_bucket(Bucket=bucket)
    with open(file_path, "rb") as file:
        s3.Object(bucket, object_key).put(Body=file)

    file = s3.Object(bucket, object_key)
    response = file.get(Range="")
```
**Expected behavior:**
200 OK response (no partial content)

**Actual behavior:**
```bash
    response = file.get(Range="")
  File "/boto3/resources/factory.py", line 520, in do_action
    response = action(self, *args, **kwargs)
  File "/boto3/resources/action.py", line 83, in __call__
    response = getattr(parent.meta.client, operation_name)(*args, **params)
  File "/botocore/client.py", line 316, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/botocore/client.py", line 613, in _make_api_call
    operation_model, request_dict, request_context)
  File "/botocore/client.py", line 632, in _make_request
    return self._endpoint.make_request(operation_model, request_dict)
  File "/botocore/endpoint.py", line 102, in make_request
    return self._send_request(request_dict, operation_model)
  File "/botocore/endpoint.py", line 137, in _send_request
    success_response, exception):
  File "/botocore/endpoint.py", line 256, in _needs_retry
    caught_exception=caught_exception, request_dict=request_dict)
  File "/botocore/hooks.py", line 356, in emit
    return self._emitter.emit(aliased_event_name, **kwargs)
  File "/botocore/hooks.py", line 228, in emit
    return self._emit(event_name, kwargs)
  File "/botocore/hooks.py", line 211, in _emit
    response = handler(**kwargs)
  File "/botocore/retryhandler.py", line 183, in __call__
    if self._checker(attempts, response, caught_exception):
  File "/botocore/retryhandler.py", line 251, in __call__
    caught_exception)
  File "/botocore/retryhandler.py", line 269, in _should_retry
    return self._checker(attempt_number, response, caught_exception)
  File "/botocore/retryhandler.py", line 317, in __call__
    caught_exception)
  File "/botocore/retryhandler.py", line 223, in __call__
    attempt_number, caught_exception)
  File "/botocore/retryhandler.py", line 359, in _check_caught_exception
    raise caught_exception
  File "/botocore/endpoint.py", line 197, in _do_get_response
    responses = self._event_emitter.emit(event_name, request=request)
  File "/botocore/hooks.py", line 356, in emit
    return self._emitter.emit(aliased_event_name, **kwargs)
  File "/botocore/hooks.py", line 228, in emit
    return self._emit(event_name, kwargs)
  File "/botocore/hooks.py", line 211, in _emit
    response = handler(**kwargs)
  File "/moto/core/models.py", line 272, in __call__
    request, request.url, request.headers
  File "/moto/s3/responses.py", line 217, in ambiguous_response
    return self.key_response(request, full_url, headers)
  File "/moto/s3/responses.py", line 846, in key_response
    request, response_headers, response_content
  File "/moto/s3/responses.py", line 804, in _handle_range_header
    _, rspec = request.headers.get("range").split("=")
ValueError: not enough values to unpack (expected 2, got 1)
```
Note that boto3 accepts this request and performs a regular GET with a 200 response instead of requesting partial content. It is a nice touch in order to avoid if statements producing code like so:

```python
request_range_header = request.headers.get("Range", "")
response = s3_object.get(Range=request_range_header)
```


**Versions**
- moto                    1.3.14
- boto                     2.49.0
- boto3                   1.13.3
- botocore              1.16.3


Discussion:

Resolving patch:
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -971,7 +971,11 @@ def key_or_control_response(self, request, full_url, headers):
         else:
             status_code, response_headers, response_content = response
 
-        if status_code == 200 and "range" in request.headers:
+        if (
+            status_code == 200
+            and "range" in request.headers
+            and request.headers["range"] != ""
+        ):
             try:
                 return self._handle_range_header(
                     request, response_headers, response_content


Test patch:
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -4911,3 +4911,16 @@ def test_request_partial_content_should_contain_actual_content_length():
         )
         e.response["Error"]["ActualObjectSize"].should.equal("9")
         e.response["Error"]["RangeRequested"].should.equal(requested_range)
+
+
+@mock_s3
+def test_request_partial_content_without_specifying_range_should_return_full_object():
+    bucket = "bucket"
+    object_key = "key"
+    s3 = boto3.resource("s3", region_name="us-east-1")
+    s3.create_bucket(Bucket=bucket)
+    s3.Object(bucket, object_key).put(Body="some text that goes a long way")
+
+    file = s3.Object(bucket, object_key)
+    response = file.get(Range="")
+    response["ContentLength"].should.equal(30)