diff options
| -rw-r--r-- | bot/converters.py | 15 | ||||
| -rw-r--r-- | bot/exts/info/doc/_cog.py | 9 | ||||
| -rw-r--r-- | bot/exts/info/doc/_inventory_parser.py | 22 | 
3 files changed, 34 insertions, 12 deletions
diff --git a/bot/converters.py b/bot/converters.py index 8b0890830..c96e2c984 100644 --- a/bot/converters.py +++ b/bot/converters.py @@ -235,11 +235,16 @@ class Inventory(Converter):      async def convert(ctx: Context, url: str) -> t.Tuple[str, _inventory_parser.InventoryDict]:          """Convert url to Intersphinx inventory URL."""          await ctx.trigger_typing() -        if (inventory := await _inventory_parser.fetch_inventory(url)) is None: -            raise BadArgument( -                f"Failed to fetch inventory file after {_inventory_parser.FAILED_REQUEST_ATTEMPTS} attempts." -            ) -        return url, inventory +        try: +            inventory = await _inventory_parser.fetch_inventory(url) +        except _inventory_parser.InvalidHeaderError: +            raise BadArgument("Unable to parse inventory because of invalid header, check if URL is correct.") +        else: +            if inventory is None: +                raise BadArgument( +                    f"Failed to fetch inventory file after {_inventory_parser.FAILED_REQUEST_ATTEMPTS} attempts." +                ) +            return url, inventory  class Snowflake(IDConverter): diff --git a/bot/exts/info/doc/_cog.py b/bot/exts/info/doc/_cog.py index 1624c50f6..e7710db24 100644 --- a/bot/exts/info/doc/_cog.py +++ b/bot/exts/info/doc/_cog.py @@ -22,7 +22,7 @@ from bot.utils.lock import SharedEvent, lock  from bot.utils.messages import send_denial, wait_for_deletion  from bot.utils.scheduling import Scheduler  from . import NAMESPACE, PRIORITY_PACKAGES, _batch_parser, doc_cache -from ._inventory_parser import InventoryDict, fetch_inventory +from ._inventory_parser import InvalidHeaderError, InventoryDict, fetch_inventory  log = logging.getLogger(__name__) @@ -137,7 +137,12 @@ class DocCog(commands.Cog):          The first attempt is rescheduled to execute in `FETCH_RESCHEDULE_DELAY.first` minutes, the subsequent attempts          in `FETCH_RESCHEDULE_DELAY.repeated` minutes.          """ -        package = await fetch_inventory(inventory_url) +        try: +            package = await fetch_inventory(inventory_url) +        except InvalidHeaderError as e: +            # Do not reschedule if the header is invalid, as the request went through but the contents are invalid. +            log.warning(f"Invalid inventory header at {inventory_url}. Reason: {e}") +            return          if not package:              if api_package_name in self.inventory_scheduler: diff --git a/bot/exts/info/doc/_inventory_parser.py b/bot/exts/info/doc/_inventory_parser.py index 80d5841a0..61924d070 100644 --- a/bot/exts/info/doc/_inventory_parser.py +++ b/bot/exts/info/doc/_inventory_parser.py @@ -16,6 +16,10 @@ _V2_LINE_RE = re.compile(r'(?x)(.+?)\s+(\S*:\S*)\s+(-?\d+)\s+?(\S*)\s+(.*)')  InventoryDict = DefaultDict[str, List[Tuple[str, str]]] +class InvalidHeaderError(Exception): +    """Raised when an inventory file has an invalid header.""" + +  class ZlibStreamReader:      """Class used for decoding zlib data of a stream line by line.""" @@ -80,19 +84,25 @@ async def _fetch_inventory(url: str) -> InventoryDict:          stream = response.content          inventory_header = (await stream.readline()).decode().rstrip() -        inventory_version = int(inventory_header[-1:]) -        await stream.readline()  # skip project name -        await stream.readline()  # skip project version +        try: +            inventory_version = int(inventory_header[-1:]) +        except ValueError: +            raise InvalidHeaderError("Unable to convert inventory version header.") + +        has_project_header = (await stream.readline()).startswith(b"# Project") +        has_version_header = (await stream.readline()).startswith(b"# Version") +        if not (has_project_header and has_version_header): +            raise InvalidHeaderError("Inventory missing project or version header.")          if inventory_version == 1:              return await _load_v1(stream)          elif inventory_version == 2:              if b"zlib" not in await stream.readline(): -                raise ValueError(f"Invalid inventory file at url {url}.") +                raise InvalidHeaderError("'zlib' not found in header of compressed inventory.")              return await _load_v2(stream) -        raise ValueError(f"Invalid inventory file at url {url}.") +        raise InvalidHeaderError("Incompatible inventory version.")  async def fetch_inventory(url: str) -> Optional[InventoryDict]: @@ -115,6 +125,8 @@ async def fetch_inventory(url: str) -> Optional[InventoryDict]:                  f"Failed to get inventory from {url}; "                  f"trying again ({attempt}/{FAILED_REQUEST_ATTEMPTS})."              ) +        except InvalidHeaderError: +            raise          except Exception:              log.exception(                  f"An unexpected error has occurred during fetching of {url}; "  |