diff options
author | David Lassig <d.lassig@t-online.de> | 2024-12-10 16:54:23 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-10 16:54:23 +0100 |
commit | 106909db8b730480615f4a33de0eb5b710944e78 (patch) | |
tree | 30225d0e6080ba64972a232cdf6a0977fd0be5ad | |
parent | Added None/empty notes to regex_search (#84437) (diff) | |
download | ansible-106909db8b730480615f4a33de0eb5b710944e78.tar.xz ansible-106909db8b730480615f4a33de0eb5b710944e78.zip |
adding option for form-multipart data to switch multipart encoding (#80566)
* adding option for form-multipart data to switch multipart encoding from default base64 to 7or8bit encoding
Co-authored-by: davlas <david.lassig@bwi.de>
Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
Co-authored-by: Sloane Hertel <19572925+s-hertel@users.noreply.github.com>
-rw-r--r-- | changelogs/fragments/feature-uri-add-option-multipart-encoding.yml | 2 | ||||
-rw-r--r-- | lib/ansible/module_utils/urls.py | 21 | ||||
-rw-r--r-- | lib/ansible/modules/uri.py | 3 | ||||
-rw-r--r-- | test/integration/targets/uri/tasks/main.yml | 4 | ||||
-rw-r--r-- | test/units/module_utils/urls/fixtures/multipart.txt | 9 | ||||
-rw-r--r-- | test/units/module_utils/urls/test_prepare_multipart.py | 13 |
6 files changed, 49 insertions, 3 deletions
diff --git a/changelogs/fragments/feature-uri-add-option-multipart-encoding.yml b/changelogs/fragments/feature-uri-add-option-multipart-encoding.yml new file mode 100644 index 0000000000..be53360b95 --- /dev/null +++ b/changelogs/fragments/feature-uri-add-option-multipart-encoding.yml @@ -0,0 +1,2 @@ +minor_changes: + - AnsibleModule.uri - Add option ``multipart_encoding`` for ``form-multipart`` files in body to change default base64 encoding for files diff --git a/lib/ansible/module_utils/urls.py b/lib/ansible/module_utils/urls.py index c90f0b78fd..282210b27a 100644 --- a/lib/ansible/module_utils/urls.py +++ b/lib/ansible/module_utils/urls.py @@ -30,6 +30,7 @@ this code instead. from __future__ import annotations import base64 +import email.encoders import email.mime.application import email.mime.multipart import email.mime.nonmultipart @@ -1045,6 +1046,7 @@ def prepare_multipart(fields): filename = None elif isinstance(value, Mapping): filename = value.get('filename') + multipart_encoding_str = value.get('multipart_encoding') or 'base64' content = value.get('content') if not any((filename, content)): raise ValueError('at least one of filename or content must be provided') @@ -1056,14 +1058,16 @@ def prepare_multipart(fields): except Exception: mime = 'application/octet-stream' main_type, sep, sub_type = mime.partition('/') + else: raise TypeError( 'value must be a string, or mapping, cannot be type %s' % value.__class__.__name__ ) if not content and filename: + multipart_encoding = set_multipart_encoding(multipart_encoding_str) with open(to_bytes(filename, errors='surrogate_or_strict'), 'rb') as f: - part = email.mime.application.MIMEApplication(f.read()) + part = email.mime.application.MIMEApplication(f.read(), _encoder=multipart_encoding) del part['Content-Type'] part.add_header('Content-Type', '%s/%s' % (main_type, sub_type)) else: @@ -1102,11 +1106,24 @@ def prepare_multipart(fields): ) +def set_multipart_encoding(encoding): + """Takes an string with specific encoding type for multipart data. + Will return reference to function from email.encoders library. + If given string key doesn't exist it will raise a ValueError""" + encoders_dict = { + "base64": email.encoders.encode_base64, + "7or8bit": email.encoders.encode_7or8bit + } + if encoders_dict.get(encoding): + return encoders_dict.get(encoding) + else: + raise ValueError("multipart_encoding must be one of %s." % repr(encoders_dict.keys())) + + # # Module-related functions # - def basic_auth_header(username, password): """Takes a username and password and returns a byte string suitable for using as value of an Authorization header to do basic auth. diff --git a/lib/ansible/modules/uri.py b/lib/ansible/modules/uri.py index 6562cfc866..b193d0ac06 100644 --- a/lib/ansible/modules/uri.py +++ b/lib/ansible/modules/uri.py @@ -61,6 +61,7 @@ options: or list of tuples into an C(application/x-www-form-urlencoded) string. (Added in v2.7) - If O(body_format) is set to V(form-multipart) it will convert a dictionary into C(multipart/form-multipart) body. (Added in v2.10) + - If C(body_format) is set to V(form-multipart) the option 'multipart_encoding' allows to change multipart file encoding. (Added in v2.19) type: raw body_format: description: @@ -308,10 +309,12 @@ EXAMPLES = r""" file1: filename: /bin/true mime_type: application/octet-stream + multipart_encoding: base64 file2: content: text based file content filename: fake.txt mime_type: text/plain + multipart_encoding: 7or8bit text_form_field: value - name: Connect to website using a previously stored cookie diff --git a/test/integration/targets/uri/tasks/main.yml b/test/integration/targets/uri/tasks/main.yml index b156f82cb9..232684936b 100644 --- a/test/integration/targets/uri/tasks/main.yml +++ b/test/integration/targets/uri/tasks/main.yml @@ -435,6 +435,9 @@ content: text based file content filename: fake.txt mime_type: text/plain + file3: + filename: formdata.txt + multipart_encoding: '7or8bit' text_form_field1: value1 text_form_field2: content: value2 @@ -446,6 +449,7 @@ that: - multipart.json.files.file1 | b64decode == '_multipart/form-data_\n' - multipart.json.files.file2 == 'text based file content' + - multipart.json.files.file3 == '_multipart/form-data_\r\n' - multipart.json.form.text_form_field1 == 'value1' - multipart.json.form.text_form_field2 == 'value2' diff --git a/test/units/module_utils/urls/fixtures/multipart.txt b/test/units/module_utils/urls/fixtures/multipart.txt index c80a1b81c1..fc2e9a80a9 100644 --- a/test/units/module_utils/urls/fixtures/multipart.txt +++ b/test/units/module_utils/urls/fixtures/multipart.txt @@ -144,6 +144,15 @@ ZG9ja2VyIGltYWdlOgoKYW5zaWJsZS9hbnNpYmxlQHNoYTI1NjpmYTVkZWY4YzI5NGZjNTA4MTNh ZjEzMWMwYjU3Mzc1OTRkODUyYWJhYzljYmU3YmEzOGUxN2JmMWM4NDc2ZjNmCg==
--===============3996062709511591449==
+Content-Transfer-Encoding: 7bit
+Content-Type: text/plain
+Content-Disposition: form-data; name="file7"; filename="client.txt"
+
+client.pem and client.key were retrieved from httptester docker image:
+
+ansible/ansible@sha256:fa5def8c294fc50813af131c0b5737594d852abac9cbe7ba38e17bf1c8476f3f
+
+--===============3996062709511591449==
Content-Type: text/plain
Content-Disposition: form-data; name="form_field_1"
diff --git a/test/units/module_utils/urls/test_prepare_multipart.py b/test/units/module_utils/urls/test_prepare_multipart.py index 5b81c39ce3..10afdd0eb5 100644 --- a/test/units/module_utils/urls/test_prepare_multipart.py +++ b/test/units/module_utils/urls/test_prepare_multipart.py @@ -59,6 +59,11 @@ def test_prepare_multipart(): }, 'file6': { 'filename': client_txt, + 'multipart_encoding': 'base64' + }, + 'file7': { + 'filename': client_txt, + 'multipart_encoding': '7or8bit' }, } @@ -69,7 +74,6 @@ def test_prepare_multipart(): assert headers.get_content_type() == 'multipart/form-data' boundary = headers.get_boundary() assert boundary is not None - with open(multipart, 'rb') as f: b_expected = f.read().replace(fixture_boundary, boundary.encode()) @@ -93,6 +97,13 @@ def test_unknown_mime(mocker): assert b'Content-Type: application/octet-stream' in b_data +def test_unknown_wrong_multipart_encoding(): + here = os.path.dirname(__file__) + example_file = os.path.join(here, 'fixtures/client.pem') + fields = {'foo': {'filename': example_file, 'multipart_encoding': 'unknown'}} + pytest.raises(ValueError, prepare_multipart, fields) + + def test_bad_mime(mocker): fields = {'foo': {'filename': 'foo.boom', 'content': 'foo'}} mocker.patch('mimetypes.guess_type', side_effect=TypeError) |