get_url: Fix regex for GNU digest line (#86134)

* GNU digest line may contain multiple spaces between
  checksum and filename. Fix regex to handle this situation.

Fixes: #86132

Signed-off-by: Abhijeet Kasurde <Akasurde@redhat.com>
This commit is contained in:
Abhijeet Kasurde
2025-11-17 15:29:43 -08:00
committed by GitHub
parent e2ae13cf38
commit af9009b00d
4 changed files with 126 additions and 32 deletions

View File

@@ -0,0 +1,3 @@
---
bugfixes:
- get_url - fix regex for GNU Digest line which is used in comparing checksums (https://github.com/ansible/ansible/issues/86132).

View File

@@ -477,17 +477,13 @@ def is_url(checksum):
return urlsplit(checksum).scheme in supported_schemes
def parse_digest_lines(filename, lines):
def parse_digest_lines(filename: str, lines: list[str]) -> list[tuple[str, str]]:
"""Returns a list of tuple containing the filename and digest depending upon
the lines provided
Args:
filename (str): Name of the filename, used only when the digest is one-liner
lines (list): A list of lines containing filenames and checksums
"""
checksum_map = []
BSD_DIGEST_LINE = re.compile(r'^(\w+) ?\((?P<path>.+)\) ?= (?P<digest>[\w.]+)$')
GNU_DIGEST_LINE = re.compile(r'^(?P<digest>[\w.]+) ([ *])(?P<path>.+)$')
GNU_DIGEST_LINE = re.compile(r'^(?P<digest>[\w.]+)\s+(\*|\.\/|\.)?(?P<path>.+)$')
if len(lines) == 1 and len(lines[0].split()) == 1:
# Only a single line with a single string

View File

@@ -1,20 +1,6 @@
# Test code for the get_url module
# (c) 2014, Richard Isaacson <richard.c.isaacson@gmail.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <https://www.gnu.org/licenses/>.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- name: Determine if python looks like it will support modern ssl features like SNI
command: "{{ ansible_python.executable }} -c 'from ssl import SSLContext'"
@@ -332,13 +318,13 @@
- name: create src file
copy:
dest: '{{ files_dir }}/27617.txt'
content: "ptux"
- name: create duplicate src file
copy:
dest: '{{ files_dir }}/71420.txt'
dest: '{{ files_dir }}/{{ item }}.txt'
content: "ptux"
loop:
- 27617
- 71420
- 86132
- 86132_single_space
- name: create sha1 checksum file of src
copy:
@@ -346,6 +332,8 @@
content: |
a97e6837f60cec6da4491bab387296bbcd72bdba 27617.txt
a97e6837f60cec6da4491bab387296bbcd72bdba 71420.txt
a97e6837f60cec6da4491bab387296bbcd72bdba 86132.txt
a97e6837f60cec6da4491bab387296bbcd72bdba 86132_single_space.txt
3911340502960ca33aece01129234460bfeb2791 not_target1.txt
1b4b6adf30992cedb0f6edefd6478ff0a593b2e4 not_target2.txt
@@ -355,6 +343,8 @@
content: |
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. 27617.txt
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. 71420.txt
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. 86132.txt
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. 86132_single_space.txt
30949cc401e30ac494d695ab8764a9f76aae17c5d73c67f65e9b558f47eff892 not_target1.txt
d0dbfc1945bc83bf6606b770e442035f2c4e15c886ee0c22fb3901ba19900b5b not_target2.txt
@@ -364,6 +354,8 @@
content: |
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. ./27617.txt
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. ./71420.txt
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. ./86132.txt
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. ./86132_single_space.txt
30949cc401e30ac494d695ab8764a9f76aae17c5d73c67f65e9b558f47eff892 ./not_target1.txt
d0dbfc1945bc83bf6606b770e442035f2c4e15c886ee0c22fb3901ba19900b5b ./not_target2.txt
@@ -373,6 +365,8 @@
content: |
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. *27617.txt
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. *71420.txt
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. *86132.txt
b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006. *86132_single_space.txt
30949cc401e30ac494d695ab8764a9f76aae17c5d73c67f65e9b558f47eff892 *not_target1.txt
d0dbfc1945bc83bf6606b770e442035f2c4e15c886ee0c22fb3901ba19900b5b *not_target2.txt
@@ -551,6 +545,61 @@
path: "{{ remote_tmp_dir }}/27617.txt"
register: stat_result_sha256_checksum_only
- name: download 86132.txt with sha1 checksum url
get_url:
url: 'http://localhost:{{ http_port }}/86132.txt'
dest: '{{ remote_tmp_dir }}'
checksum: 'sha1:http://localhost:{{ http_port }}/sha1sum.txt'
register: result_sha1_86132
- stat:
path: "{{ remote_tmp_dir }}/86132.txt"
register: stat_result_sha1_86132
- name: download 86132.txt with sha256 checksum url
get_url:
url: 'http://localhost:{{ http_port }}/86132.txt'
dest: '{{ remote_tmp_dir }}/86132sha256.txt'
checksum: 'sha256:http://localhost:{{ http_port }}/sha256sum.txt'
register: result_sha256_86132
- stat:
path: "{{ remote_tmp_dir }}/86132.txt"
register: stat_result_sha256_86132
- name: download 86132.txt with sha256 checksum url with dot leading paths
get_url:
url: 'http://localhost:{{ http_port }}/86132.txt'
dest: '{{ remote_tmp_dir }}/86132sha256_with_dot.txt'
checksum: 'sha256:http://localhost:{{ http_port }}/sha256sum_with_dot.txt'
register: result_sha256_with_dot_86132
- stat:
path: "{{ remote_tmp_dir }}/86132sha256_with_dot.txt"
register: stat_result_sha256_with_dot_86132
- name: download 86132.txt with sha256 checksum url with asterisk leading paths
get_url:
url: 'http://localhost:{{ http_port }}/86132.txt'
dest: '{{ remote_tmp_dir }}/86132sha256_with_asterisk.txt'
checksum: 'sha256:http://localhost:{{ http_port }}/sha256sum_with_asterisk.txt'
register: result_sha256_with_asterisk_86132
- stat:
path: "{{ remote_tmp_dir }}/86132sha256_with_asterisk.txt"
register: stat_result_sha256_with_asterisk_86132
- name: download 86132_single_space.txt with sha256 checksum url
get_url:
url: 'http://localhost:{{ http_port }}/86132_single_space.txt'
dest: '{{ remote_tmp_dir }}/86132_single_space.txt'
checksum: 'sha256:http://localhost:{{ http_port }}/sha256sum.txt'
register: result_sha256_86132_single_space
- stat:
path: "{{ remote_tmp_dir }}/86132_single_space.txt"
register: stat_result_sha256_86132_single_space
- name: Assert that the file was downloaded
assert:
that:
@@ -579,6 +628,16 @@
- "stat_result_sha256_with_asterisk_71420.stat.exists == true"
- "stat_result_sha256_with_file_scheme_71420.stat.exists == true"
- "stat_result_sha256_checksum_only.stat.exists == true"
- result_sha1_86132 is changed
- result_sha256_86132 is changed
- result_sha256_with_dot_86132 is changed
- result_sha256_with_asterisk_86132 is changed
- "stat_result_sha1_86132.stat.exists == true"
- "stat_result_sha256_86132.stat.exists == true"
- "stat_result_sha256_with_dot_86132.stat.exists == true"
- "stat_result_sha256_with_asterisk_86132.stat.exists == true"
- result_sha256_86132_single_space is changed
- "stat_result_sha256_86132_single_space.stat.exists == true"
- name: Test for incomplete data read (issue 85164)
get_url:

View File

@@ -8,22 +8,59 @@ import pytest
from ansible.modules.get_url import parse_digest_lines
FILENAME = "sample.txt"
@pytest.mark.parametrize(
("lines", "expected"),
[
pytest.param(
[
"2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25 sample.txt",
],
[("2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25", FILENAME)],
id="single-line-digest-single-space",
),
pytest.param(
[
"2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25 sample.txt",
],
[("2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25", FILENAME)],
id="single-line-digest-multiple-spaces",
),
pytest.param(
[
"2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25 .sample.txt",
],
[("2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25", FILENAME)],
id="single-line-digest-multiple-spaces-with-dot",
),
pytest.param(
[
"2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25 *sample.txt",
],
[("2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25", FILENAME)],
id="single-line-digest-multiple-spaces-with-asterisk",
),
pytest.param(
[
"2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25 ./sample.txt",
],
[("2a32d433bf82355a3f78318a5affa21866c9a98b151785494b386e6b08f40b25", FILENAME)],
id="single-line-digest-multiple-spaces-with-dot-and-slash",
),
pytest.param(
[
"a97e6837f60cec6da4491bab387296bbcd72bdba",
],
[("a97e6837f60cec6da4491bab387296bbcd72bdba", "sample.txt")],
[("a97e6837f60cec6da4491bab387296bbcd72bdba", FILENAME)],
id="single-line-digest",
),
pytest.param(
[
"a97e6837f60cec6da4491bab387296bbcd72bdba sample.txt",
],
[("a97e6837f60cec6da4491bab387296bbcd72bdba", "sample.txt")],
[("a97e6837f60cec6da4491bab387296bbcd72bdba", FILENAME)],
id="GNU-style-digest",
),
pytest.param(
@@ -33,7 +70,7 @@ from ansible.modules.get_url import parse_digest_lines
[
(
"b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006.",
"sample.txt",
FILENAME,
)
],
id="BSD-style-digest",
@@ -41,5 +78,4 @@ from ansible.modules.get_url import parse_digest_lines
],
)
def test_parse_digest_lines(lines, expected):
filename = "sample.txt"
assert parse_digest_lines(filename, lines) == expected
assert parse_digest_lines(filename=FILENAME, lines=lines) == expected