Repository: getmoto/moto

Issue:
S3 will not return '/' in CommonPrefixes when there is.
## Reporting Bugs

# Description
When there is some Key with consecutive slashes `'/'`, `list_objects_v2` with `Delimiter='/'` will not include the right answer in `CommonPrefixes`

# Code to reproduce
```python
import boto3
from moto import mock_s3

@mock_s3
def list_slashes():
    c = boto3.client('s3')
    c.create_bucket(Bucket='bucket')
    c.put_object(Bucket='bucket', Key='///')

    resp = c.list_objects_v2(Bucket='bucket', Prefix='', Delimiter='/')
    print(resp)

    resp = c.list_objects_v2(Bucket='bucket', Prefix='/', Delimiter='/')
    print(resp)

list_slashes()
```

# Expected
```python
{
  ...,
 'CommonPrefixes': [{'Prefix': '/'}],
 'KeyCount': 1,
  ...,
}
{
  ...,
 'CommonPrefixes': [{'Prefix': '//'}],
 'KeyCount': 1,
  ...,
}
```

# Reality
```python
{
   ...,
   'Contents': [{'Key': '', ..., }],
   'KeyCount': 1,
   ...
}
{
   ...,
   'KeyCount': 0,
   ...,
}
```

sample output from real aws S3
```python
import boto3
c = boto3.client('s3')
c.put_object(Bucket='oss-playground', Key='///') 
print(c.list_objects_v2(Bucket='oss-playground', Prefix='//', Delimiter='/'))

```
Output
```python
{
 ...,
 'IsTruncated': False,
 'Name': 'oss-playground',
 'Prefix': '//',
 'Delimiter': '/',
 'CommonPrefixes': [{'Prefix': '///'}],
 'KeyCount': 1}
```


Discussion:
This issue seems to impact other functions (e.g. `download_file`): the leadings `/` are ignored:
```python
from moto import mock_s3
import boto3

@mock_s3 
def test(): 
    client = boto3.client('s3') 
    client.create_bucket(Bucket='bucket') 
    client.put_object(Bucket='bucket', Key='key') 
    print(client.head_object(Bucket='bucket', Key='/key')) 

test()
```

This may be related to #1637 (and associated fix #2398).

Resolving patch:
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -134,7 +134,8 @@
 
 
 def parse_key_name(pth):
-    return pth.lstrip("/")
+    # strip the first '/' left by urlparse
+    return pth[1:] if pth.startswith("/") else pth
 
 
 def is_delete_keys(request, path, bucket_name):
diff --git a/moto/s3/urls.py b/moto/s3/urls.py
--- a/moto/s3/urls.py
+++ b/moto/s3/urls.py
@@ -15,5 +15,5 @@
     # path-based bucket + key
     "{0}/(?P<bucket_name_path>[^/]+)/(?P<key_name>.+)": S3ResponseInstance.key_or_control_response,
     # subdomain bucket + key with empty first part of path
-    "{0}//(?P<key_name>.*)$": S3ResponseInstance.key_or_control_response,
+    "{0}/(?P<key_name>/.*)$": S3ResponseInstance.key_or_control_response,
 }


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
@@ -3744,6 +3744,28 @@ def test_root_dir_with_empty_name_works():
     store_and_read_back_a_key("/")
 
 
+@parameterized(["mybucket", "my.bucket"])
+@mock_s3
+def test_leading_slashes_not_removed(bucket_name):
+    """Make sure that leading slashes are not removed internally."""
+    s3 = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
+    s3.create_bucket(Bucket=bucket_name)
+
+    uploaded_key = "/key"
+    invalid_key_1 = "key"
+    invalid_key_2 = "//key"
+
+    s3.put_object(Bucket=bucket_name, Key=uploaded_key, Body=b"Some body")
+
+    with assert_raises(ClientError) as e:
+        s3.get_object(Bucket=bucket_name, Key=invalid_key_1)
+    e.exception.response["Error"]["Code"].should.equal("NoSuchKey")
+
+    with assert_raises(ClientError) as e:
+        s3.get_object(Bucket=bucket_name, Key=invalid_key_2)
+    e.exception.response["Error"]["Code"].should.equal("NoSuchKey")
+
+
 @parameterized(
     [("foo/bar/baz",), ("foo",), ("foo/run_dt%3D2019-01-01%252012%253A30%253A00",)]
 )