We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Use URL patterns for content.host_blocking.whitelist
For historical reasons, the `content.host_blocking.whitelist` setting uses [fnmatch](https://docs.python.org/3/library/fnmatch.html) (see `is_whitelisted_host` in `browser/adblock.py`).

Now that we have [URL patterns](https://github.com/qutebrowser/qutebrowser/blob/master/qutebrowser/utils/urlmatch.py), it should use those instead.
Use URL patterns for content.host_blocking.whitelist
For historical reasons, the `content.host_blocking.whitelist` setting uses [fnmatch](https://docs.python.org/3/library/fnmatch.html) (see `is_whitelisted_host` in `browser/adblock.py`).

Now that we have [URL patterns](https://github.com/qutebrowser/qutebrowser/blob/master/qutebrowser/utils/urlmatch.py), it should use those instead.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `qutebrowser/browser/adblock.py`
Content:
```
1 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2 
3 # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
4 #
5 # This file is part of qutebrowser.
6 #
7 # qutebrowser is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # qutebrowser is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.
19 
20 """Functions related to ad blocking."""
21 
22 import io
23 import os.path
24 import functools
25 import posixpath
26 import zipfile
27 import fnmatch
28 
29 from qutebrowser.browser import downloads
30 from qutebrowser.config import config
31 from qutebrowser.utils import objreg, standarddir, log, message
32 from qutebrowser.commands import cmdutils
33 
34 
35 def guess_zip_filename(zf):
36     """Guess which file to use inside a zip file.
37 
38     Args:
39         zf: A ZipFile instance.
40     """
41     files = zf.namelist()
42     if len(files) == 1:
43         return files[0]
44     else:
45         for e in files:
46             if posixpath.splitext(e)[0].lower() == 'hosts':
47                 return e
48     raise FileNotFoundError("No hosts file found in zip")
49 
50 
51 def get_fileobj(byte_io):
52     """Get a usable file object to read the hosts file from."""
53     byte_io.seek(0)  # rewind downloaded file
54     if zipfile.is_zipfile(byte_io):
55         byte_io.seek(0)  # rewind what zipfile.is_zipfile did
56         zf = zipfile.ZipFile(byte_io)
57         filename = guess_zip_filename(zf)
58         byte_io = zf.open(filename, mode='r')
59     else:
60         byte_io.seek(0)  # rewind what zipfile.is_zipfile did
61     return byte_io
62 
63 
64 def is_whitelisted_host(host):
65     """Check if the given host is on the adblock whitelist.
66 
67     Args:
68         host: The host of the request as string.
69     """
70     for pattern in config.val.content.host_blocking.whitelist:
71         if fnmatch.fnmatch(host, pattern.lower()):
72             return True
73     return False
74 
75 
76 class FakeDownload:
77 
78     """A download stub to use on_download_finished with local files."""
79 
80     def __init__(self, fileobj):
81         self.basename = os.path.basename(fileobj.name)
82         self.fileobj = fileobj
83         self.successful = True
84 
85 
86 class HostBlocker:
87 
88     """Manage blocked hosts based from /etc/hosts-like files.
89 
90     Attributes:
91         _blocked_hosts: A set of blocked hosts.
92         _config_blocked_hosts: A set of blocked hosts from ~/.config.
93         _in_progress: The DownloadItems which are currently downloading.
94         _done_count: How many files have been read successfully.
95         _local_hosts_file: The path to the blocked-hosts file.
96         _config_hosts_file: The path to a blocked-hosts in ~/.config
97     """
98 
99     def __init__(self):
100         self._blocked_hosts = set()
101         self._config_blocked_hosts = set()
102         self._in_progress = []
103         self._done_count = 0
104 
105         data_dir = standarddir.data()
106         self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')
107         self._update_files()
108 
109         config_dir = standarddir.config()
110         self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')
111 
112         config.instance.changed.connect(self._update_files)
113 
114     def is_blocked(self, url):
115         """Check if the given URL (as QUrl) is blocked."""
116         if not config.val.content.host_blocking.enabled:
117             return False
118         host = url.host()
119         return ((host in self._blocked_hosts or
120                  host in self._config_blocked_hosts) and
121                 not is_whitelisted_host(host))
122 
123     def _read_hosts_file(self, filename, target):
124         """Read hosts from the given filename.
125 
126         Args:
127             filename: The file to read.
128             target: The set to store the hosts in.
129 
130         Return:
131             True if a read was attempted, False otherwise
132         """
133         if not os.path.exists(filename):
134             return False
135 
136         try:
137             with open(filename, 'r', encoding='utf-8') as f:
138                 for line in f:
139                     target.add(line.strip())
140         except (OSError, UnicodeDecodeError):
141             log.misc.exception("Failed to read host blocklist!")
142 
143         return True
144 
145     def read_hosts(self):
146         """Read hosts from the existing blocked-hosts file."""
147         self._blocked_hosts = set()
148 
149         self._read_hosts_file(self._config_hosts_file,
150                               self._config_blocked_hosts)
151 
152         found = self._read_hosts_file(self._local_hosts_file,
153                                       self._blocked_hosts)
154 
155         if not found:
156             args = objreg.get('args')
157             if (config.val.content.host_blocking.lists and
158                     args.basedir is None and
159                     config.val.content.host_blocking.enabled):
160                 message.info("Run :adblock-update to get adblock lists.")
161 
162     @cmdutils.register(instance='host-blocker')
163     def adblock_update(self):
164         """Update the adblock block lists.
165 
166         This updates `~/.local/share/qutebrowser/blocked-hosts` with downloaded
167         host lists and re-reads `~/.config/qutebrowser/blocked-hosts`.
168         """
169         self._read_hosts_file(self._config_hosts_file,
170                               self._config_blocked_hosts)
171         self._blocked_hosts = set()
172         self._done_count = 0
173         download_manager = objreg.get('qtnetwork-download-manager')
174         for url in config.val.content.host_blocking.lists:
175             if url.scheme() == 'file':
176                 filename = url.toLocalFile()
177                 try:
178                     fileobj = open(filename, 'rb')
179                 except OSError as e:
180                     message.error("adblock: Error while reading {}: {}".format(
181                         filename, e.strerror))
182                     continue
183                 download = FakeDownload(fileobj)
184                 self._in_progress.append(download)
185                 self.on_download_finished(download)
186             else:
187                 fobj = io.BytesIO()
188                 fobj.name = 'adblock: ' + url.host()
189                 target = downloads.FileObjDownloadTarget(fobj)
190                 download = download_manager.get(url, target=target,
191                                                 auto_remove=True)
192                 self._in_progress.append(download)
193                 download.finished.connect(
194                     functools.partial(self.on_download_finished, download))
195 
196     def _parse_line(self, line):
197         """Parse a line from a host file.
198 
199         Args:
200             line: The bytes object to parse.
201 
202         Returns:
203             True if parsing succeeded, False otherwise.
204         """
205         if line.startswith(b'#'):
206             # Ignoring comments early so we don't have to care about
207             # encoding errors in them.
208             return True
209 
210         try:
211             line = line.decode('utf-8')
212         except UnicodeDecodeError:
213             log.misc.error("Failed to decode: {!r}".format(line))
214             return False
215 
216         # Remove comments
217         try:
218             hash_idx = line.index('#')
219             line = line[:hash_idx]
220         except ValueError:
221             pass
222 
223         line = line.strip()
224         # Skip empty lines
225         if not line:
226             return True
227 
228         parts = line.split()
229         if len(parts) == 1:
230             # "one host per line" format
231             hosts = [parts[0]]
232         else:
233             # /etc/hosts format
234             hosts = parts[1:]
235 
236         for host in hosts:
237             if ('.' in host and
238                     not host.endswith('.localdomain') and
239                     host != '0.0.0.0'):
240                 self._blocked_hosts.add(host)
241 
242         return True
243 
244     def _merge_file(self, byte_io):
245         """Read and merge host files.
246 
247         Args:
248             byte_io: The BytesIO object of the completed download.
249 
250         Return:
251             A set of the merged hosts.
252         """
253         error_count = 0
254         line_count = 0
255         try:
256             f = get_fileobj(byte_io)
257         except (OSError, zipfile.BadZipFile, zipfile.LargeZipFile,
258                 LookupError) as e:
259             message.error("adblock: Error while reading {}: {} - {}".format(
260                 byte_io.name, e.__class__.__name__, e))
261             return
262 
263         for line in f:
264             line_count += 1
265             ok = self._parse_line(line)
266             if not ok:
267                 error_count += 1
268 
269         log.misc.debug("{}: read {} lines".format(byte_io.name, line_count))
270         if error_count > 0:
271             message.error("adblock: {} read errors for {}".format(
272                 error_count, byte_io.name))
273 
274     def on_lists_downloaded(self):
275         """Install block lists after files have been downloaded."""
276         with open(self._local_hosts_file, 'w', encoding='utf-8') as f:
277             for host in sorted(self._blocked_hosts):
278                 f.write(host + '\n')
279             message.info("adblock: Read {} hosts from {} sources.".format(
280                 len(self._blocked_hosts), self._done_count))
281 
282     @config.change_filter('content.host_blocking.lists')
283     def _update_files(self):
284         """Update files when the config changed."""
285         if not config.val.content.host_blocking.lists:
286             try:
287                 os.remove(self._local_hosts_file)
288             except FileNotFoundError:
289                 pass
290             except OSError as e:
291                 log.misc.exception("Failed to delete hosts file: {}".format(e))
292 
293     def on_download_finished(self, download):
294         """Check if all downloads are finished and if so, trigger reading.
295 
296         Arguments:
297             download: The finished DownloadItem.
298         """
299         self._in_progress.remove(download)
300         if download.successful:
301             self._done_count += 1
302             try:
303                 self._merge_file(download.fileobj)
304             finally:
305                 download.fileobj.close()
306         if not self._in_progress:
307             try:
308                 self.on_lists_downloaded()
309             except OSError:
310                 log.misc.exception("Failed to write host block list!")
311
```
--- 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/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py
--- a/qutebrowser/browser/adblock.py
+++ b/qutebrowser/browser/adblock.py
@@ -24,11 +24,10 @@
 import functools
 import posixpath
 import zipfile
-import fnmatch
 
 from qutebrowser.browser import downloads
 from qutebrowser.config import config
-from qutebrowser.utils import objreg, standarddir, log, message
+from qutebrowser.utils import objreg, standarddir, log, message, urlmatch
 from qutebrowser.commands import cmdutils
 
 
@@ -61,14 +60,14 @@
     return byte_io
 
 
-def is_whitelisted_host(host):
-    """Check if the given host is on the adblock whitelist.
+def is_whitelisted_url(url):
+    """Check if the given url is on the adblock whitelist.
 
     Args:
-        host: The host of the request as string.
+        url: The url to check in QUrl form.
     """
     for pattern in config.val.content.host_blocking.whitelist:
-        if fnmatch.fnmatch(host, pattern.lower()):
+        if pattern.matches(url):
             return True
     return False
 
@@ -118,7 +117,7 @@
         host = url.host()
         return ((host in self._blocked_hosts or
                  host in self._config_blocked_hosts) and
-                not is_whitelisted_host(host))
+                not is_whitelisted_url(url))
 
     def _read_hosts_file(self, filename, target):
         """Read hosts from the given filename.

{"golden_diff": "diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py\n--- a/qutebrowser/browser/adblock.py\n+++ b/qutebrowser/browser/adblock.py\n@@ -24,11 +24,10 @@\n import functools\n import posixpath\n import zipfile\n-import fnmatch\n \n from qutebrowser.browser import downloads\n from qutebrowser.config import config\n-from qutebrowser.utils import objreg, standarddir, log, message\n+from qutebrowser.utils import objreg, standarddir, log, message, urlmatch\n from qutebrowser.commands import cmdutils\n \n \n@@ -61,14 +60,14 @@\n     return byte_io\n \n \n-def is_whitelisted_host(host):\n-    \"\"\"Check if the given host is on the adblock whitelist.\n+def is_whitelisted_url(url):\n+    \"\"\"Check if the given url is on the adblock whitelist.\n \n     Args:\n-        host: The host of the request as string.\n+        url: The url to check in QUrl form.\n     \"\"\"\n     for pattern in config.val.content.host_blocking.whitelist:\n-        if fnmatch.fnmatch(host, pattern.lower()):\n+        if pattern.matches(url):\n             return True\n     return False\n \n@@ -118,7 +117,7 @@\n         host = url.host()\n         return ((host in self._blocked_hosts or\n                  host in self._config_blocked_hosts) and\n-                not is_whitelisted_host(host))\n+                not is_whitelisted_url(url))\n \n     def _read_hosts_file(self, filename, target):\n         \"\"\"Read hosts from the given filename.\n", "issue": "Use URL patterns for content.host_blocking.whitelist\nFor historical reasons, the `content.host_blocking.whitelist` setting uses [fnmatch](https://docs.python.org/3/library/fnmatch.html) (see `is_whitelisted_host` in `browser/adblock.py`).\r\n\r\nNow that we have [URL patterns](https://github.com/qutebrowser/qutebrowser/blob/master/qutebrowser/utils/urlmatch.py), it should use those instead.\nUse URL patterns for content.host_blocking.whitelist\nFor historical reasons, the `content.host_blocking.whitelist` setting uses [fnmatch](https://docs.python.org/3/library/fnmatch.html) (see `is_whitelisted_host` in `browser/adblock.py`).\r\n\r\nNow that we have [URL patterns](https://github.com/qutebrowser/qutebrowser/blob/master/qutebrowser/utils/urlmatch.py), it should use those instead.\n", "before_files": [{"content": "# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:\n\n# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>\n#\n# This file is part of qutebrowser.\n#\n# qutebrowser is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# qutebrowser is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.\n\n\"\"\"Functions related to ad blocking.\"\"\"\n\nimport io\nimport os.path\nimport functools\nimport posixpath\nimport zipfile\nimport fnmatch\n\nfrom qutebrowser.browser import downloads\nfrom qutebrowser.config import config\nfrom qutebrowser.utils import objreg, standarddir, log, message\nfrom qutebrowser.commands import cmdutils\n\n\ndef guess_zip_filename(zf):\n    \"\"\"Guess which file to use inside a zip file.\n\n    Args:\n        zf: A ZipFile instance.\n    \"\"\"\n    files = zf.namelist()\n    if len(files) == 1:\n        return files[0]\n    else:\n        for e in files:\n            if posixpath.splitext(e)[0].lower() == 'hosts':\n                return e\n    raise FileNotFoundError(\"No hosts file found in zip\")\n\n\ndef get_fileobj(byte_io):\n    \"\"\"Get a usable file object to read the hosts file from.\"\"\"\n    byte_io.seek(0)  # rewind downloaded file\n    if zipfile.is_zipfile(byte_io):\n        byte_io.seek(0)  # rewind what zipfile.is_zipfile did\n        zf = zipfile.ZipFile(byte_io)\n        filename = guess_zip_filename(zf)\n        byte_io = zf.open(filename, mode='r')\n    else:\n        byte_io.seek(0)  # rewind what zipfile.is_zipfile did\n    return byte_io\n\n\ndef is_whitelisted_host(host):\n    \"\"\"Check if the given host is on the adblock whitelist.\n\n    Args:\n        host: The host of the request as string.\n    \"\"\"\n    for pattern in config.val.content.host_blocking.whitelist:\n        if fnmatch.fnmatch(host, pattern.lower()):\n            return True\n    return False\n\n\nclass FakeDownload:\n\n    \"\"\"A download stub to use on_download_finished with local files.\"\"\"\n\n    def __init__(self, fileobj):\n        self.basename = os.path.basename(fileobj.name)\n        self.fileobj = fileobj\n        self.successful = True\n\n\nclass HostBlocker:\n\n    \"\"\"Manage blocked hosts based from /etc/hosts-like files.\n\n    Attributes:\n        _blocked_hosts: A set of blocked hosts.\n        _config_blocked_hosts: A set of blocked hosts from ~/.config.\n        _in_progress: The DownloadItems which are currently downloading.\n        _done_count: How many files have been read successfully.\n        _local_hosts_file: The path to the blocked-hosts file.\n        _config_hosts_file: The path to a blocked-hosts in ~/.config\n    \"\"\"\n\n    def __init__(self):\n        self._blocked_hosts = set()\n        self._config_blocked_hosts = set()\n        self._in_progress = []\n        self._done_count = 0\n\n        data_dir = standarddir.data()\n        self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')\n        self._update_files()\n\n        config_dir = standarddir.config()\n        self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')\n\n        config.instance.changed.connect(self._update_files)\n\n    def is_blocked(self, url):\n        \"\"\"Check if the given URL (as QUrl) is blocked.\"\"\"\n        if not config.val.content.host_blocking.enabled:\n            return False\n        host = url.host()\n        return ((host in self._blocked_hosts or\n                 host in self._config_blocked_hosts) and\n                not is_whitelisted_host(host))\n\n    def _read_hosts_file(self, filename, target):\n        \"\"\"Read hosts from the given filename.\n\n        Args:\n            filename: The file to read.\n            target: The set to store the hosts in.\n\n        Return:\n            True if a read was attempted, False otherwise\n        \"\"\"\n        if not os.path.exists(filename):\n            return False\n\n        try:\n            with open(filename, 'r', encoding='utf-8') as f:\n                for line in f:\n                    target.add(line.strip())\n        except (OSError, UnicodeDecodeError):\n            log.misc.exception(\"Failed to read host blocklist!\")\n\n        return True\n\n    def read_hosts(self):\n        \"\"\"Read hosts from the existing blocked-hosts file.\"\"\"\n        self._blocked_hosts = set()\n\n        self._read_hosts_file(self._config_hosts_file,\n                              self._config_blocked_hosts)\n\n        found = self._read_hosts_file(self._local_hosts_file,\n                                      self._blocked_hosts)\n\n        if not found:\n            args = objreg.get('args')\n            if (config.val.content.host_blocking.lists and\n                    args.basedir is None and\n                    config.val.content.host_blocking.enabled):\n                message.info(\"Run :adblock-update to get adblock lists.\")\n\n    @cmdutils.register(instance='host-blocker')\n    def adblock_update(self):\n        \"\"\"Update the adblock block lists.\n\n        This updates `~/.local/share/qutebrowser/blocked-hosts` with downloaded\n        host lists and re-reads `~/.config/qutebrowser/blocked-hosts`.\n        \"\"\"\n        self._read_hosts_file(self._config_hosts_file,\n                              self._config_blocked_hosts)\n        self._blocked_hosts = set()\n        self._done_count = 0\n        download_manager = objreg.get('qtnetwork-download-manager')\n        for url in config.val.content.host_blocking.lists:\n            if url.scheme() == 'file':\n                filename = url.toLocalFile()\n                try:\n                    fileobj = open(filename, 'rb')\n                except OSError as e:\n                    message.error(\"adblock: Error while reading {}: {}\".format(\n                        filename, e.strerror))\n                    continue\n                download = FakeDownload(fileobj)\n                self._in_progress.append(download)\n                self.on_download_finished(download)\n            else:\n                fobj = io.BytesIO()\n                fobj.name = 'adblock: ' + url.host()\n                target = downloads.FileObjDownloadTarget(fobj)\n                download = download_manager.get(url, target=target,\n                                                auto_remove=True)\n                self._in_progress.append(download)\n                download.finished.connect(\n                    functools.partial(self.on_download_finished, download))\n\n    def _parse_line(self, line):\n        \"\"\"Parse a line from a host file.\n\n        Args:\n            line: The bytes object to parse.\n\n        Returns:\n            True if parsing succeeded, False otherwise.\n        \"\"\"\n        if line.startswith(b'#'):\n            # Ignoring comments early so we don't have to care about\n            # encoding errors in them.\n            return True\n\n        try:\n            line = line.decode('utf-8')\n        except UnicodeDecodeError:\n            log.misc.error(\"Failed to decode: {!r}\".format(line))\n            return False\n\n        # Remove comments\n        try:\n            hash_idx = line.index('#')\n            line = line[:hash_idx]\n        except ValueError:\n            pass\n\n        line = line.strip()\n        # Skip empty lines\n        if not line:\n            return True\n\n        parts = line.split()\n        if len(parts) == 1:\n            # \"one host per line\" format\n            hosts = [parts[0]]\n        else:\n            # /etc/hosts format\n            hosts = parts[1:]\n\n        for host in hosts:\n            if ('.' in host and\n                    not host.endswith('.localdomain') and\n                    host != '0.0.0.0'):\n                self._blocked_hosts.add(host)\n\n        return True\n\n    def _merge_file(self, byte_io):\n        \"\"\"Read and merge host files.\n\n        Args:\n            byte_io: The BytesIO object of the completed download.\n\n        Return:\n            A set of the merged hosts.\n        \"\"\"\n        error_count = 0\n        line_count = 0\n        try:\n            f = get_fileobj(byte_io)\n        except (OSError, zipfile.BadZipFile, zipfile.LargeZipFile,\n                LookupError) as e:\n            message.error(\"adblock: Error while reading {}: {} - {}\".format(\n                byte_io.name, e.__class__.__name__, e))\n            return\n\n        for line in f:\n            line_count += 1\n            ok = self._parse_line(line)\n            if not ok:\n                error_count += 1\n\n        log.misc.debug(\"{}: read {} lines\".format(byte_io.name, line_count))\n        if error_count > 0:\n            message.error(\"adblock: {} read errors for {}\".format(\n                error_count, byte_io.name))\n\n    def on_lists_downloaded(self):\n        \"\"\"Install block lists after files have been downloaded.\"\"\"\n        with open(self._local_hosts_file, 'w', encoding='utf-8') as f:\n            for host in sorted(self._blocked_hosts):\n                f.write(host + '\\n')\n            message.info(\"adblock: Read {} hosts from {} sources.\".format(\n                len(self._blocked_hosts), self._done_count))\n\n    @config.change_filter('content.host_blocking.lists')\n    def _update_files(self):\n        \"\"\"Update files when the config changed.\"\"\"\n        if not config.val.content.host_blocking.lists:\n            try:\n                os.remove(self._local_hosts_file)\n            except FileNotFoundError:\n                pass\n            except OSError as e:\n                log.misc.exception(\"Failed to delete hosts file: {}\".format(e))\n\n    def on_download_finished(self, download):\n        \"\"\"Check if all downloads are finished and if so, trigger reading.\n\n        Arguments:\n            download: The finished DownloadItem.\n        \"\"\"\n        self._in_progress.remove(download)\n        if download.successful:\n            self._done_count += 1\n            try:\n                self._merge_file(download.fileobj)\n            finally:\n                download.fileobj.close()\n        if not self._in_progress:\n            try:\n                self.on_lists_downloaded()\n            except OSError:\n                log.misc.exception(\"Failed to write host block list!\")\n", "path": "qutebrowser/browser/adblock.py"}], "after_files": [{"content": "# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:\n\n# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>\n#\n# This file is part of qutebrowser.\n#\n# qutebrowser is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# qutebrowser is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.\n\n\"\"\"Functions related to ad blocking.\"\"\"\n\nimport io\nimport os.path\nimport functools\nimport posixpath\nimport zipfile\n\nfrom qutebrowser.browser import downloads\nfrom qutebrowser.config import config\nfrom qutebrowser.utils import objreg, standarddir, log, message, urlmatch\nfrom qutebrowser.commands import cmdutils\n\n\ndef guess_zip_filename(zf):\n    \"\"\"Guess which file to use inside a zip file.\n\n    Args:\n        zf: A ZipFile instance.\n    \"\"\"\n    files = zf.namelist()\n    if len(files) == 1:\n        return files[0]\n    else:\n        for e in files:\n            if posixpath.splitext(e)[0].lower() == 'hosts':\n                return e\n    raise FileNotFoundError(\"No hosts file found in zip\")\n\n\ndef get_fileobj(byte_io):\n    \"\"\"Get a usable file object to read the hosts file from.\"\"\"\n    byte_io.seek(0)  # rewind downloaded file\n    if zipfile.is_zipfile(byte_io):\n        byte_io.seek(0)  # rewind what zipfile.is_zipfile did\n        zf = zipfile.ZipFile(byte_io)\n        filename = guess_zip_filename(zf)\n        byte_io = zf.open(filename, mode='r')\n    else:\n        byte_io.seek(0)  # rewind what zipfile.is_zipfile did\n    return byte_io\n\n\ndef is_whitelisted_url(url):\n    \"\"\"Check if the given url is on the adblock whitelist.\n\n    Args:\n        url: The url to check in QUrl form.\n    \"\"\"\n    for pattern in config.val.content.host_blocking.whitelist:\n        if pattern.matches(url):\n            return True\n    return False\n\n\nclass FakeDownload:\n\n    \"\"\"A download stub to use on_download_finished with local files.\"\"\"\n\n    def __init__(self, fileobj):\n        self.basename = os.path.basename(fileobj.name)\n        self.fileobj = fileobj\n        self.successful = True\n\n\nclass HostBlocker:\n\n    \"\"\"Manage blocked hosts based from /etc/hosts-like files.\n\n    Attributes:\n        _blocked_hosts: A set of blocked hosts.\n        _config_blocked_hosts: A set of blocked hosts from ~/.config.\n        _in_progress: The DownloadItems which are currently downloading.\n        _done_count: How many files have been read successfully.\n        _local_hosts_file: The path to the blocked-hosts file.\n        _config_hosts_file: The path to a blocked-hosts in ~/.config\n    \"\"\"\n\n    def __init__(self):\n        self._blocked_hosts = set()\n        self._config_blocked_hosts = set()\n        self._in_progress = []\n        self._done_count = 0\n\n        data_dir = standarddir.data()\n        self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')\n        self._update_files()\n\n        config_dir = standarddir.config()\n        self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')\n\n        config.instance.changed.connect(self._update_files)\n\n    def is_blocked(self, url):\n        \"\"\"Check if the given URL (as QUrl) is blocked.\"\"\"\n        if not config.val.content.host_blocking.enabled:\n            return False\n        host = url.host()\n        return ((host in self._blocked_hosts or\n                 host in self._config_blocked_hosts) and\n                not is_whitelisted_url(url))\n\n    def _read_hosts_file(self, filename, target):\n        \"\"\"Read hosts from the given filename.\n\n        Args:\n            filename: The file to read.\n            target: The set to store the hosts in.\n\n        Return:\n            True if a read was attempted, False otherwise\n        \"\"\"\n        if not os.path.exists(filename):\n            return False\n\n        try:\n            with open(filename, 'r', encoding='utf-8') as f:\n                for line in f:\n                    target.add(line.strip())\n        except (OSError, UnicodeDecodeError):\n            log.misc.exception(\"Failed to read host blocklist!\")\n\n        return True\n\n    def read_hosts(self):\n        \"\"\"Read hosts from the existing blocked-hosts file.\"\"\"\n        self._blocked_hosts = set()\n\n        self._read_hosts_file(self._config_hosts_file,\n                              self._config_blocked_hosts)\n\n        found = self._read_hosts_file(self._local_hosts_file,\n                                      self._blocked_hosts)\n\n        if not found:\n            args = objreg.get('args')\n            if (config.val.content.host_blocking.lists and\n                    args.basedir is None and\n                    config.val.content.host_blocking.enabled):\n                message.info(\"Run :adblock-update to get adblock lists.\")\n\n    @cmdutils.register(instance='host-blocker')\n    def adblock_update(self):\n        \"\"\"Update the adblock block lists.\n\n        This updates `~/.local/share/qutebrowser/blocked-hosts` with downloaded\n        host lists and re-reads `~/.config/qutebrowser/blocked-hosts`.\n        \"\"\"\n        self._read_hosts_file(self._config_hosts_file,\n                              self._config_blocked_hosts)\n        self._blocked_hosts = set()\n        self._done_count = 0\n        download_manager = objreg.get('qtnetwork-download-manager')\n        for url in config.val.content.host_blocking.lists:\n            if url.scheme() == 'file':\n                filename = url.toLocalFile()\n                try:\n                    fileobj = open(filename, 'rb')\n                except OSError as e:\n                    message.error(\"adblock: Error while reading {}: {}\".format(\n                        filename, e.strerror))\n                    continue\n                download = FakeDownload(fileobj)\n                self._in_progress.append(download)\n                self.on_download_finished(download)\n            else:\n                fobj = io.BytesIO()\n                fobj.name = 'adblock: ' + url.host()\n                target = downloads.FileObjDownloadTarget(fobj)\n                download = download_manager.get(url, target=target,\n                                                auto_remove=True)\n                self._in_progress.append(download)\n                download.finished.connect(\n                    functools.partial(self.on_download_finished, download))\n\n    def _parse_line(self, line):\n        \"\"\"Parse a line from a host file.\n\n        Args:\n            line: The bytes object to parse.\n\n        Returns:\n            True if parsing succeeded, False otherwise.\n        \"\"\"\n        if line.startswith(b'#'):\n            # Ignoring comments early so we don't have to care about\n            # encoding errors in them.\n            return True\n\n        try:\n            line = line.decode('utf-8')\n        except UnicodeDecodeError:\n            log.misc.error(\"Failed to decode: {!r}\".format(line))\n            return False\n\n        # Remove comments\n        try:\n            hash_idx = line.index('#')\n            line = line[:hash_idx]\n        except ValueError:\n            pass\n\n        line = line.strip()\n        # Skip empty lines\n        if not line:\n            return True\n\n        parts = line.split()\n        if len(parts) == 1:\n            # \"one host per line\" format\n            hosts = [parts[0]]\n        else:\n            # /etc/hosts format\n            hosts = parts[1:]\n\n        for host in hosts:\n            if ('.' in host and\n                    not host.endswith('.localdomain') and\n                    host != '0.0.0.0'):\n                self._blocked_hosts.add(host)\n\n        return True\n\n    def _merge_file(self, byte_io):\n        \"\"\"Read and merge host files.\n\n        Args:\n            byte_io: The BytesIO object of the completed download.\n\n        Return:\n            A set of the merged hosts.\n        \"\"\"\n        error_count = 0\n        line_count = 0\n        try:\n            f = get_fileobj(byte_io)\n        except (OSError, zipfile.BadZipFile, zipfile.LargeZipFile,\n                LookupError) as e:\n            message.error(\"adblock: Error while reading {}: {} - {}\".format(\n                byte_io.name, e.__class__.__name__, e))\n            return\n\n        for line in f:\n            line_count += 1\n            ok = self._parse_line(line)\n            if not ok:\n                error_count += 1\n\n        log.misc.debug(\"{}: read {} lines\".format(byte_io.name, line_count))\n        if error_count > 0:\n            message.error(\"adblock: {} read errors for {}\".format(\n                error_count, byte_io.name))\n\n    def on_lists_downloaded(self):\n        \"\"\"Install block lists after files have been downloaded.\"\"\"\n        with open(self._local_hosts_file, 'w', encoding='utf-8') as f:\n            for host in sorted(self._blocked_hosts):\n                f.write(host + '\\n')\n            message.info(\"adblock: Read {} hosts from {} sources.\".format(\n                len(self._blocked_hosts), self._done_count))\n\n    @config.change_filter('content.host_blocking.lists')\n    def _update_files(self):\n        \"\"\"Update files when the config changed.\"\"\"\n        if not config.val.content.host_blocking.lists:\n            try:\n                os.remove(self._local_hosts_file)\n            except FileNotFoundError:\n                pass\n            except OSError as e:\n                log.misc.exception(\"Failed to delete hosts file: {}\".format(e))\n\n    def on_download_finished(self, download):\n        \"\"\"Check if all downloads are finished and if so, trigger reading.\n\n        Arguments:\n            download: The finished DownloadItem.\n        \"\"\"\n        self._in_progress.remove(download)\n        if download.successful:\n            self._done_count += 1\n            try:\n                self._merge_file(download.fileobj)\n            finally:\n                download.fileobj.close()\n        if not self._in_progress:\n            try:\n                self.on_lists_downloaded()\n            except OSError:\n                log.misc.exception(\"Failed to write host block list!\")\n", "path": "qutebrowser/browser/adblock.py"}]}