Krikri::PluginHelpers::HTTPDownload
HTTPDownload - shared binary-safe HTTP download with redirect following, used by get_url.cr and deb822_repository.cr. Both plugins previously hand-rolled their own near-identical download logic (each with its own redirect loop and body streaming); this centralizes the common behavior so it can't drift apart (e.g. one plugin learning redirect handling while the other doesn't).
The download is binary-safe: response.body_io is streamed straight to disk, matching how get_url.cr's own download always worked. A plain HTTP::Client#get with a String-returning body would UTF-8-decode and corrupt arbitrary binary data (GPG key bytes, archives, etc.), so never "fix" this by reading into a String.
Constants
X509_V_OK: the store verified the chain, so a handshake that failed anyway did so outside certificate verification - Crystal's own wording is the honest one there.
Class methods
to_native() of whatever get_url's copyfileobj handler caught. An OS error on the write (a full staging filesystem: "[Errno 28] No space left on device") is Python's bare str(OSError) - errno and strerror with NO path, because the call that failed is the file object's own write, which raises with filename=None. Crystal's IO::Error message names the staging file ("write (<path>): <strerror>"), which ansible never prints, so rebuild the Python shape from the errno it carries.
Downloads url to dest, following up to max_redirects
redirects and streaming the raw body byte-for-byte. Returns nil on
success; raises FetchError on a response other than 200 (including
304, which Ansible's own url_get handles one branch higher up),
raises on too many redirects, and on an unsupported scheme.
Same as #download but returns the final hop's URL and response headers (see Result).
auth_attempted tracks the 401-challenge retry: with
force_basic_auth: false (the real-Ansible default) the first
request goes out WITHOUT Authorization and exactly one retry is
made WITH it when the server answers 401 - a second 401 then
surfaces as the failure it is instead of looping.
Python words a TLS read timeout differently from a plaintext one: ssl reads surface ssl.SSLError("The read operation timed out"), while a plain socket read is the bare socket.timeout's "timed out". Every read on the connection times out that way - the wait for the response head as much as the body copy - so both fetch_url's OSError text and get_url's copyfileobj text depend on the URL scheme, not on anything in the response.