prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Import utils fetch_archive_from_http url parsing URL format assumption
  **Describe the bug**
  The function `fetch_archive_from_http` from import utils makes the assumption that a URL ends with the file extension. This assumption prevents fetching an archive from a URL that contains a query after the file extension. See also [RFC 3986](http://tools.ietf.org/html/rfc3986#section-3).
  
  Fetching an archive from an [Amazon S3 presigned URL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html) is one use case that results in this bug.
  
  **Error message**
  `Skipped url https:/<S3_BUCKET_NAME>.s3.<REGION>.amazonaws.com/filename.tar.xz?response-content-disposition=inline&X-Amz-Security-Token=<TOKEN>&X-Amz-Algorithm=<X-AMZ-ALGORITHM>&X-Amz-Date=<X-AMZ-DATE>&X-Amz-SignedHeaders=host&X-Amz-Expires=<X-AMZ-EXPIRES>&X-Amz-Credential=<CREDENTIAL>&X-Amz-Signature=<SIGNATURE> as file type is not supported here. See haystack documentation for support of more file types`
  
  **Expected behavior**
  I expect the function to fetch the archive without an error.
  
  **Additional context**
  Add any other context about the problem here, like document types / preprocessing steps / settings of reader etc.
  
  **To Reproduce**
  1. Create an Amazon S3 bucket.
  2. Upload a tar.xz archive into your Amazon S3 bucket.
  3. Create an [Amazon S3 presigned URL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html) for your archive.
  4. Fetch your archive from the Amazon S# presigned URL
  ```python
  from haystack.utils import fetch_archive_from_http
  
  url = "<YOUR_PRESIGNED_URL_FROM_STEP_3>"
  fetch_archive_from_http(url, output_dir="my_archive_dir")
  ```
  
  **FAQ Check**
  - [x] Have you had a look at [our new FAQ page](https://docs.haystack.deepset.ai/docs/faq)?
  
  **System:**
   - OS: n/a
   - GPU/CPU: n/a
   - Haystack version (commit or version number): v1.17.x
   - DocumentStore: n/a
   - Reader: n/a
   - Retriever: n/a
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `haystack/utils/import_utils.py`
  Content:
  ```
  1 import io
  2 import gzip
  3 import tarfile
  4 import zipfile
  5 import logging
  6 import importlib
  7 import importlib.util
  8 from pathlib import Path
  9 from typing import Optional, Dict, Union, Tuple, List
  10 
  11 import requests
  12 
  13 from haystack.errors import DatasetsError
  14 from haystack.schema import Document
  15 
  16 
  17 logger = logging.getLogger(__name__)
  18 
  19 
  20 def load_documents_from_hf_datasets(dataset_name: str, split: Optional[str] = "train") -> List[Document]:
  21     """
  22     Load a list of Haystack Documents from a remote Hugging Face dataset.
  23 
  24     :param dataset_name: A Hugging Face dataset containing Haystack Documents
  25     :param split: The split of the Hugging Face dataset to load from. By default, this is set to "train".
  26     :return: a List of Haystack Documents
  27     """
  28     try:
  29         from datasets import load_dataset, load_dataset_builder
  30     except ImportError:
  31         raise ImportError(
  32             "Failed to import `datasets`, Run 'pip install datasets>=2.6.0' "
  33             "to install the datasets library to use this function."
  34         )
  35 
  36     dataset = load_dataset_builder(dataset_name)
  37     if "content" not in dataset.info.features.keys():
  38         raise DatasetsError(
  39             f"{dataset_name} does not contain a `content` field which is required by Haystack to "
  40             f"create `Document` objects."
  41         )
  42 
  43     remote_dataset = load_dataset(dataset_name, split=split)
  44     documents = [Document.from_dict(document) for document in remote_dataset]
  45 
  46     return documents
  47 
  48 
  49 def fetch_archive_from_http(
  50     url: str,
  51     output_dir: str,
  52     proxies: Optional[Dict[str, str]] = None,
  53     timeout: Union[float, Tuple[float, float]] = 10.0,
  54 ) -> bool:
  55     """
  56     Fetch an archive (zip, gz or tar.gz) from a url via http and extract content to an output directory.
  57 
  58     :param url: http address
  59     :param output_dir: local path
  60     :param proxies: proxies details as required by requests library
  61     :param timeout: How many seconds to wait for the server to send data before giving up,
  62         as a float, or a :ref:`(connect timeout, read timeout) <timeouts>` tuple.
  63         Defaults to 10 seconds.
  64     :return: if anything got fetched
  65     """
  66     # verify & prepare local directory
  67     path = Path(output_dir)
  68     if not path.exists():
  69         path.mkdir(parents=True)
  70 
  71     is_not_empty = len(list(Path(path).rglob("*"))) > 0
  72     if is_not_empty:
  73         logger.info("Found data stored in '%s'. Delete this first if you really want to fetch new data.", output_dir)
  74         return False
  75     else:
  76         logger.info("Fetching from %s to '%s'", url, output_dir)
  77 
  78         _, _, archive_extension = url.rpartition(".")
  79         request_data = requests.get(url, proxies=proxies, timeout=timeout)
  80 
  81         if archive_extension == "zip":
  82             zip_archive = zipfile.ZipFile(io.BytesIO(request_data.content))
  83             zip_archive.extractall(output_dir)
  84         elif archive_extension == "gz" and not "tar.gz" in url:
  85             gzip_archive = gzip.GzipFile(fileobj=io.BytesIO(request_data.content))
  86             file_content = gzip_archive.read()
  87             file_name = url.split("/")[-1][: -(len(archive_extension) + 1)]
  88             with open(f"{output_dir}/{file_name}", "wb") as file:
  89                 file.write(file_content)
  90         elif archive_extension in ["gz", "bz2", "xz"]:
  91             tar_archive = tarfile.open(fileobj=io.BytesIO(request_data.content), mode="r|*")
  92             tar_archive.extractall(output_dir)
  93         else:
  94             logger.warning(
  95                 "Skipped url %s as file type is not supported here. "
  96                 "See haystack documentation for support of more file types",
  97                 url,
  98             )
  99 
  100         return True
  101 
  102 
  103 def is_whisper_available():
  104     return importlib.util.find_spec("whisper") is not None
  105
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/haystack/utils/import_utils.py b/haystack/utils/import_utils.py
  --- a/haystack/utils/import_utils.py
  +++ b/haystack/utils/import_utils.py
  @@ -7,6 +7,8 @@
   import importlib.util
   from pathlib import Path
   from typing import Optional, Dict, Union, Tuple, List
  +from urllib.parse import urlparse, unquote
  +from os.path import splitext, basename
   
   import requests
   
  @@ -93,6 +95,20 @@
       return documents
   
   
  +def get_filename_extension_from_url(url: str) -> Tuple[str, str]:
  +    """
  +    Extracts the filename and file extension from an url.
  +
  +    :param url: http address
  +    :return: Tuple (filename, file extension) of the file at the url.
  +    """
  +    parsed = urlparse(url)
  +    root, extension = splitext(parsed.path)
  +    archive_extension = extension[1:]
  +    file_name = unquote(basename(root[1:]))
  +    return file_name, archive_extension
  +
  +
   def fetch_archive_from_http(
       url: str,
       output_dir: str,
  @@ -122,7 +138,7 @@
       else:
           logger.info("Fetching from %s to '%s'", url, output_dir)
   
  -        _, _, archive_extension = url.rpartition(".")
  +        file_name, archive_extension = get_filename_extension_from_url(url)
           request_data = requests.get(url, proxies=proxies, timeout=timeout)
   
           if archive_extension == "zip":
  @@ -131,7 +147,6 @@
           elif archive_extension == "gz" and not "tar.gz" in url:
               gzip_archive = gzip.GzipFile(fileobj=io.BytesIO(request_data.content))
               file_content = gzip_archive.read()
  -            file_name = url.split("/")[-1][: -(len(archive_extension) + 1)]
               with open(f"{output_dir}/{file_name}", "wb") as file:
                   file.write(file_content)
           elif archive_extension in ["gz", "bz2", "xz"]:
  
verification_info: {"golden_diff": "diff --git a/haystack/utils/import_utils.py b/haystack/utils/import_utils.py\n--- a/haystack/utils/import_utils.py\n+++ b/haystack/utils/import_utils.py\n@@ -7,6 +7,8 @@\n import importlib.util\n from pathlib import Path\n from typing import Optional, Dict, Union, Tuple, List\n+from urllib.parse import urlparse, unquote\n+from os.path import splitext, basename\n \n import requests\n \n@@ -93,6 +95,20 @@\n     return documents\n \n \n+def get_filename_extension_from_url(url: str) -> Tuple[str, str]:\n+    \"\"\"\n+    Extracts the filename and file extension from an url.\n+\n+    :param url: http address\n+    :return: Tuple (filename, file extension) of the file at the url.\n+    \"\"\"\n+    parsed = urlparse(url)\n+    root, extension = splitext(parsed.path)\n+    archive_extension = extension[1:]\n+    file_name = unquote(basename(root[1:]))\n+    return file_name, archive_extension\n+\n+\n def fetch_archive_from_http(\n     url: str,\n     output_dir: str,\n@@ -122,7 +138,7 @@\n     else:\n         logger.info(\"Fetching from %s to '%s'\", url, output_dir)\n \n-        _, _, archive_extension = url.rpartition(\".\")\n+        file_name, archive_extension = get_filename_extension_from_url(url)\n         request_data = requests.get(url, proxies=proxies, timeout=timeout)\n \n         if archive_extension == \"zip\":\n@@ -131,7 +147,6 @@\n         elif archive_extension == \"gz\" and not \"tar.gz\" in url:\n             gzip_archive = gzip.GzipFile(fileobj=io.BytesIO(request_data.content))\n             file_content = gzip_archive.read()\n-            file_name = url.split(\"/\")[-1][: -(len(archive_extension) + 1)]\n             with open(f\"{output_dir}/{file_name}\", \"wb\") as file:\n                 file.write(file_content)\n         elif archive_extension in [\"gz\", \"bz2\", \"xz\"]:\n", "issue": "Import utils fetch_archive_from_http url parsing URL format assumption\n**Describe the bug**\r\nThe function `fetch_archive_from_http` from import utils makes the assumption that a URL ends with the file extension. This assumption prevents fetching an archive from a URL that contains a query after the file extension. See also [RFC 3986](http://tools.ietf.org/html/rfc3986#section-3).\r\n\r\nFetching an archive from an [Amazon S3 presigned URL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html) is one use case that results in this bug.\r\n\r\n**Error message**\r\n`Skipped url https:/<S3_BUCKET_NAME>.s3.<REGION>.amazonaws.com/filename.tar.xz?response-content-disposition=inline&X-Amz-Security-Token=<TOKEN>&X-Amz-Algorithm=<X-AMZ-ALGORITHM>&X-Amz-Date=<X-AMZ-DATE>&X-Amz-SignedHeaders=host&X-Amz-Expires=<X-AMZ-EXPIRES>&X-Amz-Credential=<CREDENTIAL>&X-Amz-Signature=<SIGNATURE> as file type is not supported here. See haystack documentation for support of more file types`\r\n\r\n**Expected behavior**\r\nI expect the function to fetch the archive without an error.\r\n\r\n**Additional context**\r\nAdd any other context about the problem here, like document types / preprocessing steps / settings of reader etc.\r\n\r\n**To Reproduce**\r\n1. Create an Amazon S3 bucket.\r\n2. Upload a tar.xz archive into your Amazon S3 bucket.\r\n3. Create an [Amazon S3 presigned URL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html) for your archive.\r\n4. Fetch your archive from the Amazon S# presigned URL\r\n```python\r\nfrom haystack.utils import fetch_archive_from_http\r\n\r\nurl = \"<YOUR_PRESIGNED_URL_FROM_STEP_3>\"\r\nfetch_archive_from_http(url, output_dir=\"my_archive_dir\")\r\n```\r\n\r\n**FAQ Check**\r\n- [x] Have you had a look at [our new FAQ page](https://docs.haystack.deepset.ai/docs/faq)?\r\n\r\n**System:**\r\n - OS: n/a\r\n - GPU/CPU: n/a\r\n - Haystack version (commit or version number): v1.17.x\r\n - DocumentStore: n/a\r\n - Reader: n/a\r\n - Retriever: n/a\r\n\n", "before_files": [{"content": "import io\nimport gzip\nimport tarfile\nimport zipfile\nimport logging\nimport importlib\nimport importlib.util\nfrom pathlib import Path\nfrom typing import Optional, Dict, Union, Tuple, List\n\nimport requests\n\nfrom haystack.errors import DatasetsError\nfrom haystack.schema import Document\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef load_documents_from_hf_datasets(dataset_name: str, split: Optional[str] = \"train\") -> List[Document]:\n    \"\"\"\n    Load a list of Haystack Documents from a remote Hugging Face dataset.\n\n    :param dataset_name: A Hugging Face dataset containing Haystack Documents\n    :param split: The split of the Hugging Face dataset to load from. By default, this is set to \"train\".\n    :return: a List of Haystack Documents\n    \"\"\"\n    try:\n        from datasets import load_dataset, load_dataset_builder\n    except ImportError:\n        raise ImportError(\n            \"Failed to import `datasets`, Run 'pip install datasets>=2.6.0' \"\n            \"to install the datasets library to use this function.\"\n        )\n\n    dataset = load_dataset_builder(dataset_name)\n    if \"content\" not in dataset.info.features.keys():\n        raise DatasetsError(\n            f\"{dataset_name} does not contain a `content` field which is required by Haystack to \"\n            f\"create `Document` objects.\"\n        )\n\n    remote_dataset = load_dataset(dataset_name, split=split)\n    documents = [Document.from_dict(document) for document in remote_dataset]\n\n    return documents\n\n\ndef fetch_archive_from_http(\n    url: str,\n    output_dir: str,\n    proxies: Optional[Dict[str, str]] = None,\n    timeout: Union[float, Tuple[float, float]] = 10.0,\n) -> bool:\n    \"\"\"\n    Fetch an archive (zip, gz or tar.gz) from a url via http and extract content to an output directory.\n\n    :param url: http address\n    :param output_dir: local path\n    :param proxies: proxies details as required by requests library\n    :param timeout: How many seconds to wait for the server to send data before giving up,\n        as a float, or a :ref:`(connect timeout, read timeout) <timeouts>` tuple.\n        Defaults to 10 seconds.\n    :return: if anything got fetched\n    \"\"\"\n    # verify & prepare local directory\n    path = Path(output_dir)\n    if not path.exists():\n        path.mkdir(parents=True)\n\n    is_not_empty = len(list(Path(path).rglob(\"*\"))) > 0\n    if is_not_empty:\n        logger.info(\"Found data stored in '%s'. Delete this first if you really want to fetch new data.\", output_dir)\n        return False\n    else:\n        logger.info(\"Fetching from %s to '%s'\", url, output_dir)\n\n        _, _, archive_extension = url.rpartition(\".\")\n        request_data = requests.get(url, proxies=proxies, timeout=timeout)\n\n        if archive_extension == \"zip\":\n            zip_archive = zipfile.ZipFile(io.BytesIO(request_data.content))\n            zip_archive.extractall(output_dir)\n        elif archive_extension == \"gz\" and not \"tar.gz\" in url:\n            gzip_archive = gzip.GzipFile(fileobj=io.BytesIO(request_data.content))\n            file_content = gzip_archive.read()\n            file_name = url.split(\"/\")[-1][: -(len(archive_extension) + 1)]\n            with open(f\"{output_dir}/{file_name}\", \"wb\") as file:\n                file.write(file_content)\n        elif archive_extension in [\"gz\", \"bz2\", \"xz\"]:\n            tar_archive = tarfile.open(fileobj=io.BytesIO(request_data.content), mode=\"r|*\")\n            tar_archive.extractall(output_dir)\n        else:\n            logger.warning(\n                \"Skipped url %s as file type is not supported here. \"\n                \"See haystack documentation for support of more file types\",\n                url,\n            )\n\n        return True\n\n\ndef is_whisper_available():\n    return importlib.util.find_spec(\"whisper\") is not None\n", "path": "haystack/utils/import_utils.py"}], "after_files": [{"content": "import io\nimport gzip\nimport tarfile\nimport zipfile\nimport logging\nimport importlib\nimport importlib.util\nfrom pathlib import Path\nfrom typing import Optional, Dict, Union, Tuple, List\nfrom urllib.parse import urlparse, unquote\nfrom os.path import splitext, basename\n\nimport requests\n\nfrom haystack.errors import DatasetsError\nfrom haystack.schema import Document\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef safe_import(import_path: str, classname: str, dep_group: str):\n    \"\"\"\n    Method that allows the import of nodes that depend on missing dependencies.\n    These nodes can be installed one by one with project.optional-dependencies\n    (see pyproject.toml) but they need to be all imported in their respective\n    package's __init__()\n\n    Therefore, in case of an ImportError, the class to import is replaced by\n    a hollow MissingDependency function, which will throw an error when\n    inizialized.\n    \"\"\"\n    try:\n        module = importlib.import_module(import_path)\n        classs = vars(module).get(classname)\n        if classs is None:\n            raise ImportError(f\"Failed to import '{classname}' from '{import_path}'\")\n    except ImportError as ie:\n        classs = _missing_dependency_stub_factory(classname, dep_group, ie)\n    return classs\n\n\ndef _missing_dependency_stub_factory(classname: str, dep_group: str, import_error: Exception):\n    \"\"\"\n    Create custom versions of MissingDependency using the given parameters.\n    See `safe_import()`\n    \"\"\"\n\n    class MissingDependency:\n        def __init__(self, *args, **kwargs):\n            _optional_component_not_installed(classname, dep_group, import_error)\n\n        def __getattr__(self, *a, **k):\n            return None\n\n    return MissingDependency\n\n\ndef _optional_component_not_installed(component: str, dep_group: str, source_error: Exception):\n    raise ImportError(\n        f\"Failed to import '{component}', \"\n        \"which is an optional component in Haystack.\\n\"\n        f\"Run 'pip install 'farm-haystack[{dep_group}]'' \"\n        \"to install the required dependencies and make this component available.\\n\"\n        f\"(Original error: {str(source_error)})\"\n    ) from source_error\n\n\ndef load_documents_from_hf_datasets(dataset_name: str, split: Optional[str] = \"train\") -> List[Document]:\n    \"\"\"\n    Load a list of Haystack Documents from a remote Hugging Face dataset.\n\n    :param dataset_name: A Hugging Face dataset containing Haystack Documents\n    :param split: The split of the Hugging Face dataset to load from. By default, this is set to \"train\".\n    :return: a List of Haystack Documents\n    \"\"\"\n    try:\n        from datasets import load_dataset, load_dataset_builder\n    except ImportError:\n        raise ImportError(\n            \"Failed to import `datasets`, Run 'pip install datasets>=2.6.0' \"\n            \"to install the datasets library to use this function.\"\n        )\n\n    dataset = load_dataset_builder(dataset_name)\n    if \"content\" not in dataset.info.features.keys():\n        raise DatasetsError(\n            f\"{dataset_name} does not contain a `content` field which is required by Haystack to \"\n            f\"create `Document` objects.\"\n        )\n\n    remote_dataset = load_dataset(dataset_name, split=split)\n    documents = [Document.from_dict(document) for document in remote_dataset]\n\n    return documents\n\n\ndef get_filename_extension_from_url(url: str) -> Tuple[str, str]:\n    \"\"\"\n    Extracts the filename and file extension from an url.\n\n    :param url: http address\n    :return: Tuple (filename, file extension) of the file at the url.\n    \"\"\"\n    parsed = urlparse(url)\n    root, extension = splitext(parsed.path)\n    archive_extension = extension[1:]\n    file_name = unquote(basename(root[1:]))\n    return file_name, archive_extension\n\n\ndef fetch_archive_from_http(\n    url: str,\n    output_dir: str,\n    proxies: Optional[Dict[str, str]] = None,\n    timeout: Union[float, Tuple[float, float]] = 10.0,\n) -> bool:\n    \"\"\"\n    Fetch an archive (zip, gz or tar.gz) from a url via http and extract content to an output directory.\n\n    :param url: http address\n    :param output_dir: local path\n    :param proxies: proxies details as required by requests library\n    :param timeout: How many seconds to wait for the server to send data before giving up,\n        as a float, or a :ref:`(connect timeout, read timeout) <timeouts>` tuple.\n        Defaults to 10 seconds.\n    :return: if anything got fetched\n    \"\"\"\n    # verify & prepare local directory\n    path = Path(output_dir)\n    if not path.exists():\n        path.mkdir(parents=True)\n\n    is_not_empty = len(list(Path(path).rglob(\"*\"))) > 0\n    if is_not_empty:\n        logger.info(\"Found data stored in '%s'. Delete this first if you really want to fetch new data.\", output_dir)\n        return False\n    else:\n        logger.info(\"Fetching from %s to '%s'\", url, output_dir)\n\n        file_name, archive_extension = get_filename_extension_from_url(url)\n        request_data = requests.get(url, proxies=proxies, timeout=timeout)\n\n        if archive_extension == \"zip\":\n            zip_archive = zipfile.ZipFile(io.BytesIO(request_data.content))\n            zip_archive.extractall(output_dir)\n        elif archive_extension == \"gz\" and not \"tar.gz\" in url:\n            gzip_archive = gzip.GzipFile(fileobj=io.BytesIO(request_data.content))\n            file_content = gzip_archive.read()\n            with open(f\"{output_dir}/{file_name}\", \"wb\") as file:\n                file.write(file_content)\n        elif archive_extension in [\"gz\", \"bz2\", \"xz\"]:\n            tar_archive = tarfile.open(fileobj=io.BytesIO(request_data.content), mode=\"r|*\")\n            tar_archive.extractall(output_dir)\n        else:\n            logger.warning(\n                \"Skipped url %s as file type is not supported here. \"\n                \"See haystack documentation for support of more file types\",\n                url,\n            )\n\n        return True\n\n\ndef is_whisper_available():\n    return importlib.util.find_spec(\"whisper\") is not None\n", "path": "haystack/utils/import_utils.py"}]}