#!/usr/bin/python3 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import subprocess import sys import threading import urllib.request from urllib.parse import urlparse, urlunparse # This script interposes between APT and its HTTP method. APT sends queries on # stdin, and expect responses on stdout. We intercept those and change the # snapshot.debian.org URLs it requests on the fly, if the equivalent URLs # exist on deb.debian.org. URI_HEADER = "URI: " def url_exists(url): try: req = urllib.request.Request(url, method="HEAD") response = urllib.request.urlopen(req) return response.getcode() == 200 except Exception: return False def write_and_flush(fh, data): fh.write(data) fh.flush() def output_handler(proc, url_mapping, lock): for line in proc.stdout: if line.startswith(URI_HEADER): url = line[len(URI_HEADER) :].rstrip() # APT expects back the original url it requested. with lock: original_url = url_mapping.get(url, None) if original_url: write_and_flush(sys.stdout, line.replace(url, original_url)) continue write_and_flush(sys.stdout, line) def main(): proc = subprocess.Popen( ["/usr/lib/apt/methods/http"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, ) url_mapping = {} lock = threading.Lock() output_thread = threading.Thread( target=output_handler, args=(proc, url_mapping, lock), daemon=True ) output_thread.start() while True: try: line = sys.stdin.readline() except KeyboardInterrupt: # When apt cuts the connection, we receive a KeyboardInterrupt. break if not line: break if line.startswith(URI_HEADER): url = line[len(URI_HEADER) :].rstrip() url_parts = urlparse(url) # For .deb packages, if we can find the file on deb.debian.org, take it # from there instead of snapshot.debian.org, because deb.debian.org will # be much faster. Hopefully, most files will be available on deb.debian.org. if url_parts.hostname == "snapshot.debian.org" and url_parts.path.endswith( ".deb" ): # The url is assumed to be of the form # http://snapshot.debian.org/archive/section/yymmddThhmmssZ/... path_parts = url_parts.path.split("/") # urlparse().path always starts with a / so path_parts is # expected to look like ["", "archive", "section", "yymmddThhmmssZ", ...] # we want to remove "archive" and "yymmddThhmmssZ" to create an url # on deb.debian.org. path_parts.pop(3) path_parts.pop(1) modified_url = urlunparse( url_parts._replace( netloc="deb.debian.org", path="/".join(path_parts) ) ) if url_exists(modified_url): with lock: url_mapping[modified_url] = url write_and_flush(proc.stdin, line.replace(url, modified_url)) continue write_and_flush(proc.stdin, line) proc.stdin.close() output_thread.join() if __name__ == "__main__": main()