cargo: add support for auth-header-format
diff --git a/src/buildstream_plugins/sources/_utils.py b/src/buildstream_plugins/sources/_utils.py
index 27dfeb3..5ce9abc 100644
--- a/src/buildstream_plugins/sources/_utils.py
+++ b/src/buildstream_plugins/sources/_utils.py
@@ -59,25 +59,42 @@
 
 
 class _UrlOpenerCreator:
-    def __init__(self, netrc_config):
+    def __init__(self, netrc_config, auth_header_format):
         self.netrc_config = netrc_config
+        self.auth_header_format = auth_header_format
 
     def get_url_opener(self):
-        if self.netrc_config:
+        if not self.auth_header_format and self.netrc_config:
             netrc_pw_mgr = _NetrcPasswordManager(self.netrc_config)
             http_auth = urllib.request.HTTPBasicAuthHandler(netrc_pw_mgr)
             return urllib.request.build_opener(http_auth)
         return urllib.request.build_opener()
 
 
-def download_file(url, etag, directory):
-    opener_creator = _UrlOpenerCreator(_parse_netrc())
+def download_file(url, etag, directory, auth_header_format=""):
+    opener_creator = _UrlOpenerCreator(_parse_netrc(), auth_header_format)
     opener = opener_creator.get_url_opener()
     default_name = os.path.basename(url)
     request = urllib.request.Request(url)
     request.add_header("Accept", "*/*")
     request.add_header("User-Agent", "BuildStream/2")
 
+    if opener_creator.auth_header_format:
+        if not opener_creator.netrc_config:
+            raise SourceError("Authorization header format specified without supporting netrc")
+
+        parts = urllib.parse.urlsplit(url)
+        entry = opener_creator.netrc_config.authenticators(parts.hostname)
+        if not entry:
+            raise SourceError(
+                "Authorization header format specified without provided password",
+                detail="No password specified in netrc for hostname: {}".format(parts.hostname),
+            )
+
+        _, _, password = entry
+        auth_header = opener_creator.auth_header_format.format(password=password)
+        request.add_header("Authorization", auth_header)
+
     if etag is not None:
         request.add_header("If-None-Match", etag)
 
diff --git a/src/buildstream_plugins/sources/cargo.py b/src/buildstream_plugins/sources/cargo.py
index d40420a..cf02ae6 100644
--- a/src/buildstream_plugins/sources/cargo.py
+++ b/src/buildstream_plugins/sources/cargo.py
@@ -114,6 +114,8 @@
         self.mark_download_url(self.url)
         self.cargo.mark_download_url(self.url, primary=False)
 
+        self.auth_header_format = None
+
     ########################################################
     #     SourceFetcher API method implementations         #
     ########################################################
@@ -127,8 +129,10 @@
         if os.path.isfile(self._get_mirror_file()):
             return  # pragma: nocover
 
+        extra_data = {}
         # Download the crate
-        crate_url = self.cargo.translate_url(self.url, alias_override=alias_override)
+        crate_url = self.cargo.translate_url(self.url, alias_override=alias_override, extra_data=extra_data)
+        self.auth_header_format = extra_data.get("auth-header-format")
         with self.cargo.timed_activity("Downloading: {}".format(crate_url), silent_nested=True):
             sha256 = self._download(crate_url)
             if self.sha is not None and sha256 != self.sha:
@@ -210,7 +214,7 @@
             etag = None
 
         with self.cargo.tempdir() as td:
-            local_file, etag, error = download_file(url, etag, td)
+            local_file, etag, error = download_file(url, etag, td, self.auth_header_format)
 
             if error:
                 raise SourceError("{}: Error mirroring {}: {}".format(self, url, error), temporary=True)