We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Test if zip bombs are possible
We should know if zip bombs are possible currently


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nextcloudappstore/core/api/v1/release/parser.py`
Content:
```
1 import re
2 import tarfile  # type: ignore
3 import lxml.etree  # type: ignore
4 from typing import Dict, Any, Tuple
5 
6 from nextcloudappstore.core.api.v1.release import ReleaseConfig
7 from nextcloudappstore.core.versioning import pad_max_version, pad_min_version
8 from rest_framework.exceptions import APIException  # type: ignore
9 
10 
11 class MaxSizeAppMetadataXmlException(APIException):
12     pass
13 
14 
15 class InvalidAppMetadataXmlException(APIException):
16     pass
17 
18 
19 class UnsupportedAppArchiveException(APIException):
20     pass
21 
22 
23 class InvalidAppPackageStructureException(APIException):
24     pass
25 
26 
27 class XMLSyntaxError(APIException):
28     pass
29 
30 
31 class GunZipAppMetadataExtractor:
32     def __init__(self, config: ReleaseConfig) -> None:
33         """
34         :argument config the config
35         """
36         self.config = config
37         self.app_folder_regex = re.compile(r'^[a-z]+[a-z_]*$')
38 
39     def extract_app_metadata(self, archive_path: str) -> Tuple[str, str]:
40         """
41         Extracts the info.xml from an tar.gz archive
42         :argument archive_path the path to the tar.gz archive
43         :raises InvalidAppPackageStructureException if the first level folder
44         does not equal the app_id or no info.xml file could be found in the
45         appinfo folder
46         :return the info.xml as string
47         """
48         if not tarfile.is_tarfile(archive_path):
49             msg = '%s is not a valid tar.gz archive ' % archive_path
50             raise UnsupportedAppArchiveException(msg)
51 
52         with tarfile.open(archive_path, 'r:gz') as tar:
53             result = self._parse_archive(tar)
54         return result
55 
56     def _parse_archive(self, tar: Any) -> Tuple[str, str]:
57         folder = list(
58             filter(lambda name: re.match(self.app_folder_regex, name),
59                    tar.getnames()
60                    )
61         )
62         if len(folder) > 1:
63             msg = 'More than one possible app folder found'
64             raise InvalidAppPackageStructureException(msg)
65         elif len(folder) == 0:
66             msg = 'No possible app folder found. App folder must contain ' \
67                   'only lowercase ASCII characters or underscores'
68             raise InvalidAppPackageStructureException(msg)
69 
70         app_id = folder[0]
71         info_path = '%s/appinfo/info.xml' % app_id
72         try:
73             app_member = tar.getmember(app_id)
74             appinfo_member = tar.getmember('%s/appinfo' % app_id)
75             info_member = tar.getmember(info_path)
76             possible_links = [app_member, appinfo_member, info_member]
77 
78             for possible_link in possible_links:
79                 if possible_link.issym() or possible_link.islnk():
80                     msg = 'Symlinks and hard links can not be used for %s' %\
81                           possible_link
82                     raise InvalidAppPackageStructureException(msg)
83 
84             if info_member.size > self.config.max_info_size:
85                 msg = '%s was bigger than allowed %i bytes' % (
86                     info_path, self.config.max_info_size)
87                 raise MaxSizeAppMetadataXmlException(msg)
88             info_file = tar.extractfile(info_member)
89             return info_file.read().decode('utf-8'), app_id
90         except KeyError:
91             msg = 'Could not find %s file inside the archive' % info_path
92             raise InvalidAppPackageStructureException(msg)
93 
94 
95 def element_to_dict(element: Any) -> Dict:
96     type = element.get('type')
97     key = element.tag.replace('-', '_')
98     if type == 'int':
99         return {key: int(element.text)}
100     elif type == 'list':
101         return {key: list(map(element_to_dict, element.iterchildren()))}
102     elif type == 'min-version':
103         return {key: pad_min_version(element.text)}
104     elif type == 'max-version':
105         return {key: pad_max_version(element.text)}
106     elif len(list(element)) > 0:
107         contents = {}
108         for child in element.iterchildren():
109             contents.update(element_to_dict(child))
110         return {key: contents}
111     else:
112         return {key: element.text}
113 
114 
115 def parse_app_metadata(xml: str, schema: str, pre_xslt: str,
116                        xslt: str) -> Dict:
117     """
118     Parses, validates and maps the xml onto a dict
119     :argument xml the info.xml string to parse
120     :argument schema the schema xml as string
121     :argument pre_xslt xslt which is run before validation to ensure that
122     everything is in the correct order and that unknown elements are excluded
123     :argument xslt the xslt to transform it to a matching structure
124     :raises InvalidAppMetadataXmlException if the schema does not validate
125     :return the parsed xml as dict
126     """
127     parser = lxml.etree.XMLParser(resolve_entities=False, no_network=True,
128                                   remove_comments=True, load_dtd=False,
129                                   remove_blank_text=True, dtd_validation=False
130                                   )
131     try:
132         doc = lxml.etree.fromstring(bytes(xml, encoding='utf-8'), parser)
133     except lxml.etree.XMLSyntaxError as e:
134         msg = 'info.xml contains malformed xml: %s' % e
135         raise XMLSyntaxError(msg)
136     for _ in doc.iter(lxml.etree.Entity):
137         raise InvalidAppMetadataXmlException('Must not contain entities')
138     pre_transform = lxml.etree.XSLT(lxml.etree.XML(pre_xslt))
139     pre_transformed_doc = pre_transform(doc)
140     schema_doc = lxml.etree.fromstring(bytes(schema, encoding='utf-8'), parser)
141     schema = lxml.etree.XMLSchema(schema_doc)
142     try:
143         schema.assertValid(pre_transformed_doc)  # type: ignore
144     except lxml.etree.DocumentInvalid as e:
145         msg = 'info.xml did not validate: %s' % e
146         raise InvalidAppMetadataXmlException(msg)
147     transform = lxml.etree.XSLT(lxml.etree.XML(xslt))
148     transformed_doc = transform(pre_transformed_doc)
149     mapped = element_to_dict(transformed_doc.getroot())
150     return mapped
151
```
--- 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()

```

diff --git a/nextcloudappstore/core/api/v1/release/parser.py b/nextcloudappstore/core/api/v1/release/parser.py
--- a/nextcloudappstore/core/api/v1/release/parser.py
+++ b/nextcloudappstore/core/api/v1/release/parser.py
@@ -77,20 +77,44 @@
 
             for possible_link in possible_links:
                 if possible_link.issym() or possible_link.islnk():
-                    msg = 'Symlinks and hard links can not be used for %s' %\
+                    msg = 'Symlinks and hard links can not be used for %s' % \
                           possible_link
                     raise InvalidAppPackageStructureException(msg)
-
-            if info_member.size > self.config.max_info_size:
-                msg = '%s was bigger than allowed %i bytes' % (
-                    info_path, self.config.max_info_size)
-                raise MaxSizeAppMetadataXmlException(msg)
             info_file = tar.extractfile(info_member)
-            return info_file.read().decode('utf-8'), app_id
+            contents = self._stream_read_file(info_file,
+                                              self.config.max_info_size)
+            return contents, app_id
         except KeyError:
             msg = 'Could not find %s file inside the archive' % info_path
             raise InvalidAppPackageStructureException(msg)
 
+    def _stream_read_file(self, info_file: Any, max_info_size: int) -> str:
+        """
+        Instead of reading everything in one go which is vulnerable to
+        zip bombs, stream and accumulate the bytes
+        :argument info_file: buffered io reader
+        :argument max_info_size: maximum file size in bytes
+        :raises MaxSizeAppMetadataXmlException if the maximum size was reached
+        :return: the parsed info.xml
+        """
+        # FIXME: If someone finds a less ugly version, please feel free to
+        #        improve it
+        size = 0
+        result = b''
+        while True:
+            size += 1024
+            if size > max_info_size:
+                msg = 'info.xml was bigger than allowed %i bytes' % \
+                      max_info_size
+                raise MaxSizeAppMetadataXmlException(msg)
+
+            chunk = info_file.read(1024)
+            if not chunk:
+                break
+            result += chunk
+
+        return result.decode('utf-8')
+
 
 def element_to_dict(element: Any) -> Dict:
     type = element.get('type')

{"golden_diff": "diff --git a/nextcloudappstore/core/api/v1/release/parser.py b/nextcloudappstore/core/api/v1/release/parser.py\n--- a/nextcloudappstore/core/api/v1/release/parser.py\n+++ b/nextcloudappstore/core/api/v1/release/parser.py\n@@ -77,20 +77,44 @@\n \n             for possible_link in possible_links:\n                 if possible_link.issym() or possible_link.islnk():\n-                    msg = 'Symlinks and hard links can not be used for %s' %\\\n+                    msg = 'Symlinks and hard links can not be used for %s' % \\\n                           possible_link\n                     raise InvalidAppPackageStructureException(msg)\n-\n-            if info_member.size > self.config.max_info_size:\n-                msg = '%s was bigger than allowed %i bytes' % (\n-                    info_path, self.config.max_info_size)\n-                raise MaxSizeAppMetadataXmlException(msg)\n             info_file = tar.extractfile(info_member)\n-            return info_file.read().decode('utf-8'), app_id\n+            contents = self._stream_read_file(info_file,\n+                                              self.config.max_info_size)\n+            return contents, app_id\n         except KeyError:\n             msg = 'Could not find %s file inside the archive' % info_path\n             raise InvalidAppPackageStructureException(msg)\n \n+    def _stream_read_file(self, info_file: Any, max_info_size: int) -> str:\n+        \"\"\"\n+        Instead of reading everything in one go which is vulnerable to\n+        zip bombs, stream and accumulate the bytes\n+        :argument info_file: buffered io reader\n+        :argument max_info_size: maximum file size in bytes\n+        :raises MaxSizeAppMetadataXmlException if the maximum size was reached\n+        :return: the parsed info.xml\n+        \"\"\"\n+        # FIXME: If someone finds a less ugly version, please feel free to\n+        #        improve it\n+        size = 0\n+        result = b''\n+        while True:\n+            size += 1024\n+            if size > max_info_size:\n+                msg = 'info.xml was bigger than allowed %i bytes' % \\\n+                      max_info_size\n+                raise MaxSizeAppMetadataXmlException(msg)\n+\n+            chunk = info_file.read(1024)\n+            if not chunk:\n+                break\n+            result += chunk\n+\n+        return result.decode('utf-8')\n+\n \n def element_to_dict(element: Any) -> Dict:\n     type = element.get('type')\n", "issue": "Test if zip bombs are possible\nWe should know if zip bombs are possible currently\n\n", "before_files": [{"content": "import re\nimport tarfile  # type: ignore\nimport lxml.etree  # type: ignore\nfrom typing import Dict, Any, Tuple\n\nfrom nextcloudappstore.core.api.v1.release import ReleaseConfig\nfrom nextcloudappstore.core.versioning import pad_max_version, pad_min_version\nfrom rest_framework.exceptions import APIException  # type: ignore\n\n\nclass MaxSizeAppMetadataXmlException(APIException):\n    pass\n\n\nclass InvalidAppMetadataXmlException(APIException):\n    pass\n\n\nclass UnsupportedAppArchiveException(APIException):\n    pass\n\n\nclass InvalidAppPackageStructureException(APIException):\n    pass\n\n\nclass XMLSyntaxError(APIException):\n    pass\n\n\nclass GunZipAppMetadataExtractor:\n    def __init__(self, config: ReleaseConfig) -> None:\n        \"\"\"\n        :argument config the config\n        \"\"\"\n        self.config = config\n        self.app_folder_regex = re.compile(r'^[a-z]+[a-z_]*$')\n\n    def extract_app_metadata(self, archive_path: str) -> Tuple[str, str]:\n        \"\"\"\n        Extracts the info.xml from an tar.gz archive\n        :argument archive_path the path to the tar.gz archive\n        :raises InvalidAppPackageStructureException if the first level folder\n        does not equal the app_id or no info.xml file could be found in the\n        appinfo folder\n        :return the info.xml as string\n        \"\"\"\n        if not tarfile.is_tarfile(archive_path):\n            msg = '%s is not a valid tar.gz archive ' % archive_path\n            raise UnsupportedAppArchiveException(msg)\n\n        with tarfile.open(archive_path, 'r:gz') as tar:\n            result = self._parse_archive(tar)\n        return result\n\n    def _parse_archive(self, tar: Any) -> Tuple[str, str]:\n        folder = list(\n            filter(lambda name: re.match(self.app_folder_regex, name),\n                   tar.getnames()\n                   )\n        )\n        if len(folder) > 1:\n            msg = 'More than one possible app folder found'\n            raise InvalidAppPackageStructureException(msg)\n        elif len(folder) == 0:\n            msg = 'No possible app folder found. App folder must contain ' \\\n                  'only lowercase ASCII characters or underscores'\n            raise InvalidAppPackageStructureException(msg)\n\n        app_id = folder[0]\n        info_path = '%s/appinfo/info.xml' % app_id\n        try:\n            app_member = tar.getmember(app_id)\n            appinfo_member = tar.getmember('%s/appinfo' % app_id)\n            info_member = tar.getmember(info_path)\n            possible_links = [app_member, appinfo_member, info_member]\n\n            for possible_link in possible_links:\n                if possible_link.issym() or possible_link.islnk():\n                    msg = 'Symlinks and hard links can not be used for %s' %\\\n                          possible_link\n                    raise InvalidAppPackageStructureException(msg)\n\n            if info_member.size > self.config.max_info_size:\n                msg = '%s was bigger than allowed %i bytes' % (\n                    info_path, self.config.max_info_size)\n                raise MaxSizeAppMetadataXmlException(msg)\n            info_file = tar.extractfile(info_member)\n            return info_file.read().decode('utf-8'), app_id\n        except KeyError:\n            msg = 'Could not find %s file inside the archive' % info_path\n            raise InvalidAppPackageStructureException(msg)\n\n\ndef element_to_dict(element: Any) -> Dict:\n    type = element.get('type')\n    key = element.tag.replace('-', '_')\n    if type == 'int':\n        return {key: int(element.text)}\n    elif type == 'list':\n        return {key: list(map(element_to_dict, element.iterchildren()))}\n    elif type == 'min-version':\n        return {key: pad_min_version(element.text)}\n    elif type == 'max-version':\n        return {key: pad_max_version(element.text)}\n    elif len(list(element)) > 0:\n        contents = {}\n        for child in element.iterchildren():\n            contents.update(element_to_dict(child))\n        return {key: contents}\n    else:\n        return {key: element.text}\n\n\ndef parse_app_metadata(xml: str, schema: str, pre_xslt: str,\n                       xslt: str) -> Dict:\n    \"\"\"\n    Parses, validates and maps the xml onto a dict\n    :argument xml the info.xml string to parse\n    :argument schema the schema xml as string\n    :argument pre_xslt xslt which is run before validation to ensure that\n    everything is in the correct order and that unknown elements are excluded\n    :argument xslt the xslt to transform it to a matching structure\n    :raises InvalidAppMetadataXmlException if the schema does not validate\n    :return the parsed xml as dict\n    \"\"\"\n    parser = lxml.etree.XMLParser(resolve_entities=False, no_network=True,\n                                  remove_comments=True, load_dtd=False,\n                                  remove_blank_text=True, dtd_validation=False\n                                  )\n    try:\n        doc = lxml.etree.fromstring(bytes(xml, encoding='utf-8'), parser)\n    except lxml.etree.XMLSyntaxError as e:\n        msg = 'info.xml contains malformed xml: %s' % e\n        raise XMLSyntaxError(msg)\n    for _ in doc.iter(lxml.etree.Entity):\n        raise InvalidAppMetadataXmlException('Must not contain entities')\n    pre_transform = lxml.etree.XSLT(lxml.etree.XML(pre_xslt))\n    pre_transformed_doc = pre_transform(doc)\n    schema_doc = lxml.etree.fromstring(bytes(schema, encoding='utf-8'), parser)\n    schema = lxml.etree.XMLSchema(schema_doc)\n    try:\n        schema.assertValid(pre_transformed_doc)  # type: ignore\n    except lxml.etree.DocumentInvalid as e:\n        msg = 'info.xml did not validate: %s' % e\n        raise InvalidAppMetadataXmlException(msg)\n    transform = lxml.etree.XSLT(lxml.etree.XML(xslt))\n    transformed_doc = transform(pre_transformed_doc)\n    mapped = element_to_dict(transformed_doc.getroot())\n    return mapped\n", "path": "nextcloudappstore/core/api/v1/release/parser.py"}], "after_files": [{"content": "import re\nimport tarfile  # type: ignore\nimport lxml.etree  # type: ignore\nfrom typing import Dict, Any, Tuple\n\nfrom nextcloudappstore.core.api.v1.release import ReleaseConfig\nfrom nextcloudappstore.core.versioning import pad_max_version, pad_min_version\nfrom rest_framework.exceptions import APIException  # type: ignore\n\n\nclass MaxSizeAppMetadataXmlException(APIException):\n    pass\n\n\nclass InvalidAppMetadataXmlException(APIException):\n    pass\n\n\nclass UnsupportedAppArchiveException(APIException):\n    pass\n\n\nclass InvalidAppPackageStructureException(APIException):\n    pass\n\n\nclass XMLSyntaxError(APIException):\n    pass\n\n\nclass GunZipAppMetadataExtractor:\n    def __init__(self, config: ReleaseConfig) -> None:\n        \"\"\"\n        :argument config the config\n        \"\"\"\n        self.config = config\n        self.app_folder_regex = re.compile(r'^[a-z]+[a-z_]*$')\n\n    def extract_app_metadata(self, archive_path: str) -> Tuple[str, str]:\n        \"\"\"\n        Extracts the info.xml from an tar.gz archive\n        :argument archive_path the path to the tar.gz archive\n        :raises InvalidAppPackageStructureException if the first level folder\n        does not equal the app_id or no info.xml file could be found in the\n        appinfo folder\n        :return the info.xml as string\n        \"\"\"\n        if not tarfile.is_tarfile(archive_path):\n            msg = '%s is not a valid tar.gz archive ' % archive_path\n            raise UnsupportedAppArchiveException(msg)\n\n        with tarfile.open(archive_path, 'r:gz') as tar:\n            result = self._parse_archive(tar)\n        return result\n\n    def _parse_archive(self, tar: Any) -> Tuple[str, str]:\n        folder = list(\n            filter(lambda name: re.match(self.app_folder_regex, name),\n                   tar.getnames()\n                   )\n        )\n        if len(folder) > 1:\n            msg = 'More than one possible app folder found'\n            raise InvalidAppPackageStructureException(msg)\n        elif len(folder) == 0:\n            msg = 'No possible app folder found. App folder must contain ' \\\n                  'only lowercase ASCII characters or underscores'\n            raise InvalidAppPackageStructureException(msg)\n\n        app_id = folder[0]\n        info_path = '%s/appinfo/info.xml' % app_id\n        try:\n            app_member = tar.getmember(app_id)\n            appinfo_member = tar.getmember('%s/appinfo' % app_id)\n            info_member = tar.getmember(info_path)\n            possible_links = [app_member, appinfo_member, info_member]\n\n            for possible_link in possible_links:\n                if possible_link.issym() or possible_link.islnk():\n                    msg = 'Symlinks and hard links can not be used for %s' % \\\n                          possible_link\n                    raise InvalidAppPackageStructureException(msg)\n            info_file = tar.extractfile(info_member)\n            contents = self._stream_read_file(info_file,\n                                              self.config.max_info_size)\n            return contents, app_id\n        except KeyError:\n            msg = 'Could not find %s file inside the archive' % info_path\n            raise InvalidAppPackageStructureException(msg)\n\n    def _stream_read_file(self, info_file: Any, max_info_size: int) -> str:\n        \"\"\"\n        Instead of reading everything in one go which is vulnerable to\n        zip bombs, stream and accumulate the bytes\n        :argument info_file: buffered io reader\n        :argument max_info_size: maximum file size in bytes\n        :raises MaxSizeAppMetadataXmlException if the maximum size was reached\n        :return: the parsed info.xml\n        \"\"\"\n        # FIXME: If someone finds a less ugly version, please feel free to\n        #        improve it\n        size = 0\n        result = b''\n        while True:\n            size += 1024\n            if size > max_info_size:\n                msg = 'info.xml was bigger than allowed %i bytes' % \\\n                      max_info_size\n                raise MaxSizeAppMetadataXmlException(msg)\n\n            chunk = info_file.read(1024)\n            if not chunk:\n                break\n            result += chunk\n\n        return result.decode('utf-8')\n\n\ndef element_to_dict(element: Any) -> Dict:\n    type = element.get('type')\n    key = element.tag.replace('-', '_')\n    if type == 'int':\n        return {key: int(element.text)}\n    elif type == 'list':\n        return {key: list(map(element_to_dict, element.iterchildren()))}\n    elif type == 'min-version':\n        return {key: pad_min_version(element.text)}\n    elif type == 'max-version':\n        return {key: pad_max_version(element.text)}\n    elif len(list(element)) > 0:\n        contents = {}\n        for child in element.iterchildren():\n            contents.update(element_to_dict(child))\n        return {key: contents}\n    else:\n        return {key: element.text}\n\n\ndef parse_app_metadata(xml: str, schema: str, pre_xslt: str,\n                       xslt: str) -> Dict:\n    \"\"\"\n    Parses, validates and maps the xml onto a dict\n    :argument xml the info.xml string to parse\n    :argument schema the schema xml as string\n    :argument pre_xslt xslt which is run before validation to ensure that\n    everything is in the correct order and that unknown elements are excluded\n    :argument xslt the xslt to transform it to a matching structure\n    :raises InvalidAppMetadataXmlException if the schema does not validate\n    :return the parsed xml as dict\n    \"\"\"\n    parser = lxml.etree.XMLParser(resolve_entities=False, no_network=True,\n                                  remove_comments=True, load_dtd=False,\n                                  remove_blank_text=True, dtd_validation=False\n                                  )\n    try:\n        doc = lxml.etree.fromstring(bytes(xml, encoding='utf-8'), parser)\n    except lxml.etree.XMLSyntaxError as e:\n        msg = 'info.xml contains malformed xml: %s' % e\n        raise XMLSyntaxError(msg)\n    for _ in doc.iter(lxml.etree.Entity):\n        raise InvalidAppMetadataXmlException('Must not contain entities')\n    pre_transform = lxml.etree.XSLT(lxml.etree.XML(pre_xslt))\n    pre_transformed_doc = pre_transform(doc)\n    schema_doc = lxml.etree.fromstring(bytes(schema, encoding='utf-8'), parser)\n    schema = lxml.etree.XMLSchema(schema_doc)\n    try:\n        schema.assertValid(pre_transformed_doc)  # type: ignore\n    except lxml.etree.DocumentInvalid as e:\n        msg = 'info.xml did not validate: %s' % e\n        raise InvalidAppMetadataXmlException(msg)\n    transform = lxml.etree.XSLT(lxml.etree.XML(xslt))\n    transformed_doc = transform(pre_transformed_doc)\n    mapped = element_to_dict(transformed_doc.getroot())\n    return mapped\n", "path": "nextcloudappstore/core/api/v1/release/parser.py"}]}