summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Lassig <d.lassig@t-online.de>2024-12-10 16:54:23 +0100
committerGitHub <noreply@github.com>2024-12-10 16:54:23 +0100
commit106909db8b730480615f4a33de0eb5b710944e78 (patch)
tree30225d0e6080ba64972a232cdf6a0977fd0be5ad
parentAdded None/empty notes to regex_search (#84437) (diff)
downloadansible-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.yml2
-rw-r--r--lib/ansible/module_utils/urls.py21
-rw-r--r--lib/ansible/modules/uri.py3
-rw-r--r--test/integration/targets/uri/tasks/main.yml4
-rw-r--r--test/units/module_utils/urls/fixtures/multipart.txt9
-rw-r--r--test/units/module_utils/urls/test_prepare_multipart.py13
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)