diff --git a/minio/__init__.py b/minio/__init__.py index 303945a61..a59ad98b2 100644 --- a/minio/__init__.py +++ b/minio/__init__.py @@ -33,7 +33,7 @@ __title__ = "minio-py" __author__ = "MinIO, Inc." -__version__ = "7.2.8" +__version__ = "7.2.9" __license__ = "Apache 2.0" __copyright__ = "Copyright 2015, 2016, 2017, 2018, 2019, 2020 MinIO, Inc." diff --git a/minio/api.py b/minio/api.py index 56af05781..025834044 100644 --- a/minio/api.py +++ b/minio/api.py @@ -65,7 +65,7 @@ from .helpers import (_DEFAULT_USER_AGENT, MAX_MULTIPART_COUNT, MAX_MULTIPART_OBJECT_SIZE, MAX_PART_SIZE, MIN_PART_SIZE, BaseURL, DictType, ObjectWriteResult, ProgressType, - ThreadPool, check_bucket_name, check_non_empty_string, + ThreadPool, check_bucket_name, check_object_name, check_sse, check_ssec, genheaders, get_part_info, headers_to_strings, is_valid_policy_type, makedirs, md5sum_hash, queryencode, read_part_data, sha256_hash) @@ -594,7 +594,7 @@ def select_object_content( print(result.stats()) """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) if not isinstance(request, SelectRequest): raise ValueError("request must be SelectRequest type") body = marshal(request) @@ -1107,7 +1107,7 @@ def fget_object( ) """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) if os.path.isdir(file_path): raise ValueError(f"file {file_path} is a directory") @@ -1227,7 +1227,7 @@ def get_object( response.release_conn() """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) check_ssec(ssec) headers = cast(DictType, ssec.headers() if ssec else {}) @@ -1315,7 +1315,7 @@ def copy_object( print(result.object_name, result.version_id) """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) if not isinstance(source, CopySource): raise ValueError("source must be CopySource type") check_sse(sse) @@ -1535,7 +1535,7 @@ def compose_object( print(result.object_name, result.version_id) """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) if not isinstance(sources, (list, tuple)) or not sources: raise ValueError("sources must be non-empty list or tuple type") i = 0 @@ -1808,7 +1808,7 @@ def put_object( ) """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) check_sse(sse) if tags is not None and not isinstance(tags, Tags): raise ValueError("tags must be Tags type") @@ -2031,7 +2031,7 @@ def stat_object( """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) check_ssec(ssec) headers = cast(DictType, ssec.headers() if ssec else {}) @@ -2089,7 +2089,7 @@ def remove_object( ) """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) self._execute( "DELETE", bucket_name, @@ -2252,7 +2252,7 @@ def get_presigned_url( print(url) """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) if expires.total_seconds() < 1 or expires.total_seconds() > 604800: raise ValueError("expires must be between 1 second to 7 days") @@ -2636,7 +2636,7 @@ def delete_object_tags( client.delete_object_tags("my-bucket", "my-object") """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) query_params = {"versionId": version_id} if version_id else {} query_params["tagging"] = "" self._execute( @@ -2664,7 +2664,7 @@ def get_object_tags( tags = client.get_object_tags("my-bucket", "my-object") """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) query_params = {"versionId": version_id} if version_id else {} query_params["tagging"] = "" try: @@ -2703,7 +2703,7 @@ def set_object_tags( client.set_object_tags("my-bucket", "my-object", tags) """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) if not isinstance(tags, Tags): raise ValueError("tags must be Tags type") body = marshal(Tagging(tags)) @@ -2735,7 +2735,7 @@ def enable_object_legal_hold( client.enable_object_legal_hold("my-bucket", "my-object") """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) body = marshal(LegalHold(True)) query_params = {"versionId": version_id} if version_id else {} query_params["legal-hold"] = "" @@ -2765,7 +2765,7 @@ def disable_object_legal_hold( client.disable_object_legal_hold("my-bucket", "my-object") """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) body = marshal(LegalHold(False)) query_params = {"versionId": version_id} if version_id else {} query_params["legal-hold"] = "" @@ -2798,7 +2798,7 @@ def is_object_legal_hold_enabled( print("legal hold is not enabled on my-object") """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) query_params = {"versionId": version_id} if version_id else {} query_params["legal-hold"] = "" try: @@ -2889,7 +2889,7 @@ def get_object_retention( config = client.get_object_retention("my-bucket", "my-object") """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) query_params = {"versionId": version_id} if version_id else {} query_params["retention"] = "" try: @@ -2927,7 +2927,7 @@ def set_object_retention( client.set_object_retention("my-bucket", "my-object", config) """ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host) - check_non_empty_string(object_name) + check_object_name(object_name) if not isinstance(config, Retention): raise ValueError("config must be Retention type") body = marshal(config) diff --git a/minio/helpers.py b/minio/helpers.py index c7f6c9010..2fb11ea04 100644 --- a/minio/helpers.py +++ b/minio/helpers.py @@ -267,6 +267,16 @@ def check_non_empty_string(string: str | bytes): raise TypeError() from exc +def check_object_name(object_name: str): + """Check whether given object name is valid.""" + check_non_empty_string(object_name) + tokens = object_name.split("/") + if "." in tokens or ".." in tokens: + raise ValueError( + "object name with '.' or '..' path segment is not supported", + ) + + def is_valid_policy_type(policy: str | bytes): """ Validate if policy is type str diff --git a/minio/minioadmin.py b/minio/minioadmin.py index 75bc7f561..8da638a79 100644 --- a/minio/minioadmin.py +++ b/minio/minioadmin.py @@ -708,6 +708,14 @@ def bucket_quota_get(self, bucket: str) -> str: ) return response.data.decode() + def get_data_usage_info(self): + """Get data usage info""" + response = self._url_open( + "GET", + _COMMAND.DATA_USAGE_INFO, + ) + return response.data.decode() + def get_service_account(self, access_key: str) -> str: """Get information about service account""" response = self._url_open( diff --git a/minio/signer.py b/minio/signer.py index 3e60beb91..83cf89831 100644 --- a/minio/signer.py +++ b/minio/signer.py @@ -74,7 +74,7 @@ def _get_canonical_headers( ): values = values if isinstance(values, (list, tuple)) else [values] ordered_headers[key] = ",".join([ - _MULTI_SPACE_REGEX.sub(" ", value) for value in values + _MULTI_SPACE_REGEX.sub(" ", value).strip() for value in values ]) ordered_headers = OrderedDict(sorted(ordered_headers.items())) diff --git a/tests/functional/tests.py b/tests/functional/tests.py index 7def5ba85..f6a043f2d 100644 --- a/tests/functional/tests.py +++ b/tests/functional/tests.py @@ -692,7 +692,12 @@ def test_put_object(log_entry, sse=None): reader = LimitedRandomReader(length) log_entry["args"]["data"] = "LimitedRandomReader(11 * MB)" log_entry["args"]["metadata"] = metadata = { - 'x-amz-meta-testing': 'value', 'test-key': 'value2'} + 'x-amz-meta-testing': 'value', + 'test-key': 'value2', + "My-Project": "Project One", + "My-header1": " a b c ", + "My-Header2": "\"a b c\"", + } log_entry["args"]["content_type"] = content_type = ( "application/octet-stream") log_entry["args"]["object_name"] = object_name + "-metadata"