Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-validators-newline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "bugfix",
"category": "Validators",
"description": "Anchor the host label, region, bucket name and host prefix validators with ``\\Z`` so a value with a trailing newline is rejected instead of slipping through ``$`` into the request host."
}
4 changes: 2 additions & 2 deletions botocore/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
# to be as long as 255 characters, and bucket names can contain any
# combination of uppercase letters, lowercase letters, numbers, periods
# (.), hyphens (-), and underscores (_).
VALID_BUCKET = re.compile(r'^[a-zA-Z0-9.\-_]{1,255}$')
VALID_BUCKET = re.compile(r'^[a-zA-Z0-9.\-_]{1,255}\Z')
_ACCESSPOINT_ARN = (
r'^arn:(aws).*:(s3|s3-object-lambda):[a-z\-0-9]*:[0-9]{12}:accesspoint[/:]'
r'[a-zA-Z0-9\-.]{1,63}$'
Expand Down Expand Up @@ -1002,7 +1002,7 @@ def __call__(self, client, **kwargs):
class HeaderToHostHoister:
"""Takes a header and moves it to the front of the hoststring."""

_VALID_HOSTNAME = re.compile(r'(?!-)[a-z\d-]{1,63}(?<!-)$', re.IGNORECASE)
_VALID_HOSTNAME = re.compile(r'(?!-)[a-z\d-]{1,63}(?<!-)\Z', re.IGNORECASE)

def __init__(self, header_name):
self._header_name = header_name
Expand Down
2 changes: 1 addition & 1 deletion botocore/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
ISO8601 = '%Y-%m-%dT%H:%M:%SZ'
# Same as ISO8601, but with microsecond precision.
ISO8601_MICRO = '%Y-%m-%dT%H:%M:%S.%fZ'
HOST_PREFIX_RE = re.compile(r"^[A-Za-z0-9\.\-]+$")
HOST_PREFIX_RE = re.compile(r"^[A-Za-z0-9\.\-]+\Z")

TIMESTAMP_PRECISION_DEFAULT = 'default'
TIMESTAMP_PRECISION_MILLISECOND = 'millisecond'
Expand Down
4 changes: 2 additions & 2 deletions botocore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1335,7 +1335,7 @@ def validate_region_name(region_name):
"""Provided region_name must be a valid host label."""
if region_name is None:
return
valid_host_label = re.compile(r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{,63}(?<!-)$')
valid_host_label = re.compile(r'^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{,63}(?<!-)\Z')
valid = valid_host_label.match(region_name)
if not valid:
raise InvalidRegionError(region_name=region_name)
Expand Down Expand Up @@ -2644,7 +2644,7 @@ def _s3_addressing_handler(self):
class S3ControlEndpointSetter:
_DEFAULT_PARTITION = 'aws'
_DEFAULT_DNS_SUFFIX = 'amazonaws.com'
_HOST_LABEL_REGEX = re.compile(r'^[a-zA-Z0-9\-]{1,63}$')
_HOST_LABEL_REGEX = re.compile(r'^[a-zA-Z0-9\-]{1,63}\Z')

def __init__(
self,
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/test_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,15 @@ def test_bucket_too_long_raises_exception(self):
with self.assertRaises(ParamValidationError):
handlers.validate_bucket_name(params)

def test_trailing_newline_in_bucket_raises_exception(self):
params = {
'Bucket': 'my-bucket-name\n',
'Key': 'foo',
'Body': b'asdf',
}
with self.assertRaises(ParamValidationError):
handlers.validate_bucket_name(params)

def test_not_dns_compat_but_still_valid_bucket_name(self):
params = {
'Bucket': 'foasdf......bar--baz-a_b_CD10',
Expand Down Expand Up @@ -1623,6 +1632,10 @@ def test_does_validate_host_with_illegal_char(self):
with self.assertRaises(ParamValidationError):
self._prepend_to_host('https://example.com/path', 'host#name')

def test_does_validate_host_with_trailing_newline(self):
with self.assertRaises(ParamValidationError):
self._prepend_to_host('https://example.com/path', 'host\n')


@pytest.mark.parametrize(
'environ, header_before, header_after',
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
switch_host_s3_accelerate,
switch_to_virtual_host_style,
validate_jmespath_for_set,
validate_region_name,
)
from tests import FreezeTime, RawResponse, create_session, mock, unittest

Expand Down Expand Up @@ -1001,6 +1002,22 @@ def test_hostname_no_dots(self):
self.assertTrue(is_valid_endpoint_url('https://foo/'))


class TestValidateRegionName(unittest.TestCase):
def test_valid_region(self):
self.assertIsNone(validate_region_name('us-east-1'))

def test_none_is_allowed(self):
self.assertIsNone(validate_region_name(None))

def test_trailing_newline_is_rejected(self):
with self.assertRaises(InvalidRegionError):
validate_region_name('us-east-1\n')

def test_embedded_newline_is_rejected(self):
with self.assertRaises(InvalidRegionError):
validate_region_name('us-east\n-1')


class TestFixS3Host(unittest.TestCase):
def test_fix_s3_host_initial(self):
request = AWSRequest(
Expand Down