From f71190068b96bcb29b2f65f43b1e63a9d5c395f4 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Mon, 2 Oct 2023 15:58:35 -0500 Subject: [PATCH] [stable-2.15] Add compat function for parsing Content-Disposition header (#81807) (#81828) * py2 compat for get_param * Add tests, and handle ValueError * Add clog frag (cherry picked from commit 831dc6e) --- .../81806-py2-content-disposition.yml | 3 +++ lib/ansible/module_utils/urls.py | 19 ++++++++++++++- test/integration/targets/uri/tasks/main.yml | 24 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/81806-py2-content-disposition.yml diff --git a/changelogs/fragments/81806-py2-content-disposition.yml b/changelogs/fragments/81806-py2-content-disposition.yml new file mode 100644 index 00000000000..8002a2071d6 --- /dev/null +++ b/changelogs/fragments/81806-py2-content-disposition.yml @@ -0,0 +1,3 @@ +bugfixes: +- uri/urls - Add compat function to handle the ability to parse the filename from a Content-Disposition header + (https://github.com/ansible/ansible/issues/81806) diff --git a/lib/ansible/module_utils/urls.py b/lib/ansible/module_utils/urls.py index 942366aeb19..d9bf32fde3d 100644 --- a/lib/ansible/module_utils/urls.py +++ b/lib/ansible/module_utils/urls.py @@ -771,6 +771,18 @@ def extract_pem_certs(b_data): yield match.group(0) +def _py2_get_param(headers, param, header='content-type'): + m = httplib.HTTPMessage(io.StringIO()) + cd = headers.getheader(header) or '' + try: + m.plisttext = cd[cd.index(';'):] + m.parseplist() + except ValueError: + return None + + return m.getparam(param) + + def get_response_filename(response): url = response.geturl() path = urlparse(url)[2] @@ -778,7 +790,12 @@ def get_response_filename(response): if filename: filename = unquote(filename) - return response.headers.get_param('filename', header='content-disposition') or filename + if PY2: + get_param = functools.partial(_py2_get_param, response.headers) + else: + get_param = response.headers.get_param + + return get_param('filename', header='content-disposition') or filename def parse_content_type(response): diff --git a/test/integration/targets/uri/tasks/main.yml b/test/integration/targets/uri/tasks/main.yml index 42b04735d0b..ddae83a00df 100644 --- a/test/integration/targets/uri/tasks/main.yml +++ b/test/integration/targets/uri/tasks/main.yml @@ -766,6 +766,30 @@ dest: "{{ remote_tmp_dir }}/output" state: absent +- name: Test download root to dir without content-disposition + uri: + url: "https://{{ httpbin_host }}/" + dest: "{{ remote_tmp_dir }}" + register: get_root_no_filename + +- name: Test downloading to dir without content-disposition + uri: + url: "https://{{ httpbin_host }}/response-headers" + dest: "{{ remote_tmp_dir }}" + register: get_dir_no_filename + +- name: Test downloading to dir with content-disposition + uri: + url: 'https://{{ httpbin_host }}/response-headers?Content-Disposition=attachment%3B%20filename%3D%22filename.json%22' + dest: "{{ remote_tmp_dir }}" + register: get_dir_filename + +- assert: + that: + - get_root_no_filename.path == remote_tmp_dir ~ "/index.html" + - get_dir_no_filename.path == remote_tmp_dir ~ "/response-headers" + - get_dir_filename.path == remote_tmp_dir ~ "/filename.json" + - name: Test follow_redirects=none import_tasks: redirect-none.yml