Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions src/incatools/odk/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,36 @@ def info(tools) -> None:
then fallback to "U". This is enabled by default, unless the
provided URL already points to a compressed file.""",
)
def download(url, output, reference, cache_info, max_retry, try_gzip) -> None:
@click.option(
"--broken-hosts-file",
default="config/broken-hosts.txt",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, not so much a fan of a list.. Cant we just try and fail to decompress?

type=click.Path(path_type=Path, dir_okay=False),
help="""A file containing a list of known broken HTTP hosts, from
which downloading a gzipped file shall not be attempted
despite --try-gzip.""",
)
def download(
url, output, reference, cache_info, max_retry, try_gzip, broken_hosts_file
) -> None:
"""Download a file."""

urlpath = Path(urlparse(url).path)
urlobj = urlparse(url)
urlpath = Path(urlobj.path)
compression = Compression.from_extension(urlpath)

broken_hosts: List[str] = []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GzipFile.read() on some cases raises gzip.BadGzipFile (an OSError), not DownloadError, so the except DownloadError in the attempts loop below never catches it (like http://www.ebi.ac.uk/efo/efo.owl.gz). so we crash instead of falling back to the plain URL.

If _handle_successful_download in download.py caught the decompression error, deleted the tmp file and raised DownloadError, the existing fallback would just work, for EBI and any other server that answers 200 for things that don't exist (something like this, to get the idea):

try:
    with tmpfile.open("wb") as fd:
        for chunk in _ResponseWrapper.maybe_wrap(response, comp).iter_content(512):
            h.update(chunk)
            fd.write(chunk)
except (OSError, EOFError) as e:
    tmpfile.unlink(missing_ok=True)
    raise DownloadError(f"Cannot decompress file from {response.url}: {e}")

(EOFError is for truncated gzip. OSError also catches real disk-write errors, so maybe catch only around the decompression if you want to keep those apart.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s a good idea, thanks!

if try_gzip and broken_hosts_file.exists():
with broken_hosts_file.open("r") as f:
for line in [line.strip() for line in f]:
if line and not line.startswith("#"):
broken_hosts.append(line)

attempts: List[Tuple[str, Compression]] = []
if compression == Compression.NONE and try_gzip:
if (
compression == Compression.NONE
and try_gzip
and urlobj.netloc not in broken_hosts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

netloc includes the port and userinfo, and it's case-sensitive, so http://www.ebi.ac.uk:80/efo/efo.owl or WWW.EBI.AC.UK gets past this check. Maybe urlobj.hostname?

):
attempts.append((url + ".gz", Compression.GZIP))
attempts.append((url, compression))

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Broken HTTP hosts; these servers are known to cause issues when
# attempting to download gzipped files from them.

# The EBI server returns a homepage with a 200 status code,
# instead of a proper 404, for any unknown URL.
www.ebi.ac.uk
Loading