diff options
288 files changed, 4293 insertions, 7497 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a95ba918d..5ae20070b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.3.0 +current_version = 2.5.1 commit = True message = Release v{new_version} tag = True diff --git a/.codecov.yml b/.codecov.yml index 8646cac9a..3ceb4eeec 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,7 +1,11 @@ coverage: status: - project: false - patch: false + project: + default: + informational: true + patch: + default: + informational: true changes: false comment: false @@ -40,7 +40,10 @@ exclude = .*,__pycache__,resources.py # A003: Builtin name for class attribute (needed for overridden methods) # W503: like break before binary operator # W504: line break after binary operator -# FI15: __future__ import "generator_stop" missing +# FI18: __future__ import "annotations" missing +# PT004: fixture '{name}' does not return anything, add leading underscore +# PT011: pytest.raises(ValueError) is too broad, set the match parameter or use a more specific exception +# PT012: pytest.raises() block should contain a single simple statement ignore = B001,B008,B305, E128,E226,E265,E501,E402,E266,E722,E731, @@ -49,9 +52,12 @@ ignore = P101,P102,P103, D102,D103,D106,D107,D104,D105,D209,D211,D401,D402,D403,D412,D413, A003, - W503, W504 - FI15 -min-version = 3.6.1 + W503, W504, + FI18, + PT004, + PT011, + PT012 +min-version = 3.7.0 max-complexity = 12 per-file-ignores = qutebrowser/api/hook.py : N801 @@ -62,3 +68,6 @@ per-file-ignores = copyright-check = True copyright-regexp = # Copyright [\d-]+ .* copyright-min-file-size = 110 +pytest-fixture-no-parentheses = True +pytest-mark-no-parentheses = True +pytest-parametrize-names-type = csv diff --git a/.github/CONTRIBUTING.asciidoc b/.github/CONTRIBUTING.asciidoc index bf9f0e66b..0d03af336 100644 --- a/.github/CONTRIBUTING.asciidoc +++ b/.github/CONTRIBUTING.asciidoc @@ -5,11 +5,11 @@ open pull requests. - Before you start to work on something, please leave a comment on the relevant issue (or open one). This makes sure there is no duplicate work done. -- Either run the testsuite locally, or keep an eye on Travis CI / AppVeyor - after pushing changes. +- Either run the testsuite locally, or keep an eye on the CI at the end of the + pull request page after pushing changes. - If you are stuck somewhere or have questions, - https://github.com/qutebrowser/qutebrowser#getting-help[please ask]! + https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/index.asciidoc#getting-help[please ask]! See the link:../doc/contributing.asciidoc[full contribution documentation] for details and other useful hints. diff --git a/.github/workflows/bleeding.yml b/.github/workflows/bleeding.yml index 766f535d7..b2370357f 100644 --- a/.github/workflows/bleeding.yml +++ b/.github/workflows/bleeding.yml @@ -15,7 +15,7 @@ jobs: container: image: "qutebrowser/ci:archlinux-webengine-unstable" env: - PY_COLORS: "1" + FORCE_COLOR: "1" DOCKER: "archlinux-webengine-unstable" CI: true PYTEST_ADDOPTS: "--color=yes" @@ -58,7 +58,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: "3.10" - name: Get asciidoc uses: actions/checkout@v2 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e50ba2c60..288efbc02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: - 'dependabot/*' pull_request: env: - PY_COLORS: "1" + FORCE_COLOR: "1" MYPY_FORCE_TERMINAL_WIDTH: "180" jobs: @@ -43,7 +43,7 @@ jobs: key: "${{ matrix.testenv }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('scripts/dev/pylint_checkers/qute_pylint/*.py') }}" - uses: actions/setup-python@v2 with: - python-version: '3.8' + python-version: '3.10' - uses: actions/setup-node@v2-beta with: node-version: '12.x' @@ -53,7 +53,7 @@ jobs: - name: Install dependencies run: | [[ ${{ matrix.testenv }} == eslint ]] && npm install -g eslint - [[ ${{ matrix.testenv }} == docs ]] && sudo apt-get install --no-install-recommends asciidoc + [[ ${{ matrix.testenv }} == docs ]] && sudo apt-get update && sudo apt-get install --no-install-recommends asciidoc if [[ ${{ matrix.testenv }} == shellcheck ]]; then scversion="stable" bindir="$HOME/.local/bin" @@ -105,44 +105,45 @@ jobs: fail-fast: false matrix: include: - ### PyQt 5.12 (Python 3.6) - - testenv: py36-pyqt512 + ### PyQt 5.12 (Python 3.7) + - testenv: py37-pyqt512 os: ubuntu-18.04 - python: 3.6 + python: "3.7" ### PyQt 5.13 (Python 3.7) - testenv: py37-pyqt513 os: ubuntu-20.04 - python: 3.7 + python: "3.7" ### PyQt 5.14 (Python 3.8) - testenv: py38-pyqt514 os: ubuntu-20.04 - python: 3.8 + python: "3.8" ### PyQt 5.15.0 (Python 3.9) - testenv: py39-pyqt5150 os: ubuntu-20.04 - python: 3.9 - ### PyQt 5.15 (Python 3.9, with coverage) - - testenv: py39-pyqt515-cov + python: "3.9" + ### PyQt 5.15 (Python 3.10, with coverage) + - testenv: py310-pyqt515-cov os: ubuntu-20.04 - python: 3.9 - ### PyQt 5.15 (Python 3.10) - - testenv: py310-pyqt515 - os: ubuntu-20.04 - python: 3.10-dev + python: "3.10" + ### PyQt 5.15 (Python 3.11) + # - testenv: py311-pyqt515 + # os: ubuntu-20.04 + # python: "3.11-dev" + # experimental: true ### macOS: PyQt 5.15 (Python 3.9 to match PyInstaller env) - testenv: py39-pyqt515 os: macos-10.15 - python: 3.7 + python: "3.9" args: "tests/unit" # Only run unit tests on macOS ### macOS Big Sur # - testenv: py39-pyqt515 # os: macos-11.0 - # python: 3.9 + # python: "3.9" # args: "tests/unit" # Only run unit tests on macOS ### Windows: PyQt 5.15 (Python 3.9 to match PyInstaller env) - testenv: py39-pyqt515 os: windows-2019 - python: 3.9 + python: "3.9" runs-on: "${{ matrix.os }}" steps: - uses: actions/checkout@v2 @@ -197,12 +198,12 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: javascript, python queries: +security-extended - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 irc: timeout-minutes: 2 diff --git a/.gitignore b/.gitignore index 31c4ca3b7..ccfc12ccb 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ TODO /scripts/testbrowser/cpp/webengine/testbrowser /scripts/testbrowser/cpp/webengine/.qmake.stash /scripts/dev/pylint_checkers/qute_pylint.egg-info +/scripts/dev/pylint_checkers/build /misc/file_version_info.txt /doc/extapi/_build /misc/nsis/include @@ -1,5 +1,5 @@ [mypy] -python_version = 3.6 +python_version = 3.7 ### --strict warn_unused_configs = True @@ -20,6 +20,7 @@ strict_equality = True ### Other strictness flags warn_unreachable = True disallow_any_unimported = True +enable_error_code = ignore-without-code ### Output show_error_codes = True @@ -5,23 +5,26 @@ ignore=resources.py extension-pkg-whitelist=PyQt5,sip load-plugins=qute_pylint.config, qute_pylint.modeline, - qute_pylint.openencoding, pylint.extensions.docstyle, pylint.extensions.emptystring, - pylint.extensions.broad_try_clause, pylint.extensions.overlapping_exceptions, -persistent=n + pylint.extensions.code_style, + pylint.extensions.comparison_placement, + pylint.extensions.for_any_all, + pylint.extensions.docstyle, + pylint.extensions.check_elif, + pylint.extensions.typing, + pylint.extensions.docparams, + pylint.extensions.private_import, -[broad_try_clause] -max-try-statements=7 +persistent=n +py-version=3.7 [MESSAGES CONTROL] enable=all disable=locally-disabled, - locally-enabled, suppressed-message, fixme, - no-self-use, cyclic-import, blacklisted-name, logging-format-interpolation, @@ -46,7 +49,15 @@ disable=locally-disabled, too-many-statements, too-few-public-methods, import-outside-toplevel, - bad-continuation # This lint disagrees with Black + consider-using-f-string, + logging-fstring-interpolation, + raise-missing-from, + consider-using-tuple, + consider-using-namedtuple-or-dataclass, + missing-raises-doc, + missing-type-doc, + missing-param-doc, + useless-param-doc, [BASIC] function-rgx=[a-z_][a-z0-9_]{2,50}$ @@ -58,6 +69,7 @@ argument-rgx=[a-z_][a-z0-9_]{0,30}$ variable-rgx=[a-z_][a-z0-9_]{0,30}$ docstring-min-length=3 no-docstring-rgx=(^_|^main$) +class-const-naming-style = snake_case [FORMAT] max-line-length=88 diff --git a/MANIFEST.in b/MANIFEST.in index ed4b5e5b1..81bf824b7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,7 +4,7 @@ recursive-include qutebrowser/javascript *.js graft tests graft qutebrowser/html graft qutebrowser/3rdparty -graft icons +graft qutebrowser/icons graft doc/img graft misc/apparmor graft misc/userscripts diff --git a/README.asciidoc b/README.asciidoc index 5c87df857..fe0c23f2d 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -7,7 +7,7 @@ qutebrowser =========== // QUTE_WEB_HIDE -image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.* +image:qutebrowser/icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on Python and Qt.* image:https://github.com/qutebrowser/qutebrowser/workflows/CI/badge.svg["Build Status", link="https://github.com/qutebrowser/qutebrowser/actions?query=workflow%3ACI"] image:https://codecov.io/github/qutebrowser/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/qutebrowser/qutebrowser?branch=master"] @@ -16,7 +16,7 @@ link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[bl // QUTE_WEB_HIDE_END qutebrowser is a keyboard-focused browser with a minimal GUI. It's based -on Python and PyQt5 and free software, licensed under the GPL. +on Python and Qt and free software, licensed under the GPL. It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl. @@ -63,7 +63,7 @@ ways: * Use the built-in `:report` command or the automatic crash dialog. * Open an issue in the Github issue tracker. * Write a mail to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. For security bugs, please contact me directly at mail@qutebrowser.org, GPG ID @@ -74,7 +74,7 @@ Requirements The following software and libraries are required to run qutebrowser: -* https://www.python.org/[Python] 3.6.1 or newer +* https://www.python.org/[Python] 3.7 or newer * https://www.qt.io/[Qt] 5.12.0 or newer (5.12 LTS or 5.15 recommended, Qt 6 is not supported yet) with the following modules: - QtCore / qtbase @@ -96,10 +96,9 @@ websites and using it for transmission of sensitive data._ * https://palletsprojects.com/p/jinja/[jinja2] * https://github.com/yaml/pyyaml[PyYAML] -On older Python versions (3.6/3.7/3.8), the following backports are also required: +On older Python versions (3.7/3.8), the following backports are also required: -* https://importlib-resources.readthedocs.io/[importlib_resources] (Python 3.8 or older) -* https://github.com/ericvsmith/dataclasses[dataclasses] (Python 3.6 only) +* https://importlib-resources.readthedocs.io/[importlib_resources] The following libraries are optional: @@ -109,8 +108,8 @@ The following libraries are optional: QtWebEngine backend. * On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log output. -* https://importlib-metadata.readthedocs.io/[importlib_resources] on Python 3.7 - or older, to improve QtWebEngine version detection when PyQtWebEngine is +* https://importlib-metadata.readthedocs.io/[importlib_metadata] on Python 3.7, + to improve QtWebEngine version detection when PyQtWebEngine is installed via pip (thus, this dependency usually isn't relevant for packagers). * https://asciidoc.org/[asciidoc] to generate the documentation for the `:help` @@ -203,20 +202,16 @@ Active * https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2) * https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2) -* https://nyxt.atlas.engineer/[Nyxt browser] (formerly "Next browser", Lisp, Emacs-like but also offers Vim bindings, QtWebKit or GTK+/WebKit2 - note there was a https://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly) +* https://nyxt.atlas.engineer/[Nyxt browser] (formerly "Next browser", Lisp, Emacs-like but also offers Vim bindings, QtWebEngine or GTK+/WebKit2 - note there was a https://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution in 2019] which was handled quite badly) * https://vieb.dev/[Vieb] (JavaScript, Electron) * https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2) * Chrome/Chromium addons: - https://vimium.github.io/[Vimium], - https://github.com/dcchambers/vb4c[vb4c] (fork of cVim) + https://vimium.github.io/[Vimium] * Firefox addons (based on WebExtensions): - https://github.com/tridactyl/tridactyl[Tridactyl], - https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental), - https://github.com/ueokande/vim-vixen[Vim Vixen], - https://github.com/amedama41/vvimpulation[VVimpulation] + https://tridactyl.xyz/[Tridactyl], + https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] * Addons for Firefox and Chrome: https://github.com/brookhong/Surfingkeys[Surfingkeys], - https://krabby.netlify.com/[Krabby], https://lydell.github.io/LinkHints/[Link Hints] (hinting only) * Addons for Safari: https://televator.net/vimari/[Vimari] @@ -231,23 +226,27 @@ main inspiration for qutebrowser) QtWebEngine, https://github.com/parkouss/webmacs/issues/137[unmaintained]) * https://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with WebKit1) -* https://wiki.archlinux.org/index.php?title=Jumanji[jumanji] (C, GTK+ with WebKit1, -original site is gone but the Arch Linux wiki has some data) +* https://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1) * http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko) * https://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2) * https://github.com/conformal/xombrero[xombrero] (C, GTK+ with WebKit1) * https://github.com/linkdd/cream-browser[Cream Browser] (C, GTK+ with WebKit1) +* https://github.com/jun7/wyeb[wyeb] (C, GTK+ with WebKit2) * Firefox addons (not based on WebExtensions or no recent activity): http://www.vimperator.org/[Vimperator], http://bug.5digits.org/pentadactyl/index[Pentadactyl], https://github.com/akhodakivskiy/VimFx[VimFx] (seems to offer a https://gir.st/blog/legacyfox.htm[hack] to run on modern Firefox releases), - https://github.com/shinglyu/QuantumVim[QuantumVim] + https://github.com/shinglyu/QuantumVim[QuantumVim], + https://github.com/ueokande/vim-vixen[Vim Vixen] (ESR only), + https://github.com/amedama41/vvimpulation[VVimpulation], + https://krabby.netlify.com/[Krabby] * Chrome/Chromium addons: https://github.com/k2nr/ViChrome/[ViChrome], https://github.com/jinzhu/vrome[Vrome], https://github.com/lusakasa/saka-key[Saka Key] (https://github.com/lusakasa/saka-key/issues/171[unmaintained]), https://github.com/1995eaton/chromium-vim[cVim], + https://github.com/dcchambers/vb4c[vb4c] (fork of cVim, https://github.com/dcchambers/vb4c/issues/23#issuecomment-810694017[unmaintained]), https://glee.github.io/[GleeBox] License diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 008c693a3..f75f3faa9 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -15,17 +15,269 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Fixed` for any bug fixes. // `Security` to invite users to upgrade in case of vulnerabilities. +[[v3.0.0]] +v3.0.0 (unreleased) +------------------- + +Added +~~~~~ + +- On invalid commands/settings with a similarly spelled match, qutebrowser now + suggests the correct name in its error messages. +- New features in userscripts: + * `qutedmenu` gained new `window` and `private` options. + * `qute-keepassxc` now supports unlock-on-demand, multiple account + selection via rofi, and inserting TOTP-codes (experimental). + +Removed +~~~~~~~ + +- Support for Python 3.6 is dropped, as it's been + https://discuss.python.org/t/python-3-6-rides-into-the-sunset/12964[end-of-life upstream] + since December 2021. Python 3.7.0 or newer is now required. +- It's planned to drop support for various legacy platforms and libraries which + are unsupported upstream, such as: + * Qt before 5.15 LTS (plus adding support for Qt 6.2+) + * The QtWebKit backend + * macOS 10.14 (via Homebrew) + * 32-bit Windows (via Qt) + * Windows 8 (via Qt) + * Windows 10 before 1809 (via Qt) + * Possibly other more minor dependency changes + +Changed +~~~~~~~ + +- The qutebrowser icons got moved from `icons/` to `qutebrowser/icons` in the + repository, so that it's possible for qutebrowser to load them using Python's + resource system (rather than compiling them into a Qt resource file). + Packagers are advised to use `misc/Makefile` if possible, which has been + updated with the new paths. +- The `content.javascript.can_access_clipboard` setting got renamed to + `content.javascript.clipboard` and now understands three different values + rather than being a boolean: `none` (formerly `false`), `access` (formerly + `true`) and `access-paste` (additionally allows pasting content, needed for + websites like Photopea or GitHub Codespaces). +- The default `hints.selectors` now also match the `treeitem` ARIA roles. +- The `:click-element` command now can also click elements based on its ID + (`id`), a CSS selector (`css`), a position (`position`), or click the + currently focused element (`focused`). +- The `:click-element` command now can select the first found element via + `--select-first`. + +Fixed +~~~~~ + +- When the devtools are clicked but `input.insert_mode.auto_enter` is set to + `false`, insert mode now isn't entered anymore. + +[[v2.5.2]] +v2.5.2 (unreleased) +------------------- + +Fixed +~~~~~ + +- The `install` and `stacktrace` help pages are now included in the docs + shipped with qutebrowser when using the recommended packaging workflow. +- The Windows installer now more consistently uses the configured Windows colors +- The Windows installer now bases the desktop/start menu icon choices on the + existing install, if upgrading. + +[[v2.5.1]] +v2.5.1 (2022-05-26) +------------------- + +Fixed +~~~~~ + +- The `qute-pass` userscript is marked as executable again. +- PDF.js now works properly again with the macOS and Windows releases. +- The MathML workaround for darkmode (e.g. black on black Wikipedia formula) + now also works for display (rather than inline) math. +- The `content.proxy` setting can now correctly be set to arbitrary values via + the `qute://settings` page again. +- Fixed issues with Chromium version detection on Archlinux with + qt5-webengine 5.15.9-3. +- Fixed a rare possible crash with invalid `Content-Disposition` headers. +- Fixes for various notification-related crashes: + * With the `tiramisu` notification server (due to invalid behavior of the server, now a non-fatal error) + * With the `budgie` notification server when closing a notification (due to invalid behavior of the server, now worked around) + * When a server exits with an unsuccessful exit status (now a non-fatal error) + * When a server couldn't be started successfully (now a non-fatal error) + * With the `herbe` notification presenter, when the website tries to close + the notification after the user accepting (right-clicking) it. +- Fixes in userscripts: + * The `qute-bitwarden` userscript now correctly searches for entries for + sites on a subdomain of an unrecognized TLD. subdomain names. Previously + `my.site.local` would have searched in bitwarden for `my.sitelocal`, + losing the rightmost dot. + +[[v2.5.0]] +v2.5.0 (2022-04-01) +------------------- + +Deprecated +~~~~~~~~~~ + +- v2.5.x will be the last release of qutebrowser 2. + **For the upcoming 3.0.0 release**, it's planned to drop support for various + legacy platforms and libraries which are unsupported upstream, such as: + * Qt before 5.15 LTS (plus adding support for Qt 6.2+) + * Python 3.6 + * The QtWebKit backend + * macOS 10.14 (via Homebrew) + * 32-bit Windows (via Qt) + * Windows 8 (via Qt) + * Windows 10 before 1809 (via Qt) + * Possibly other more minor dependency changes +- The `:rl-unix-word-rubout` command (`<Ctrl-W>` in command/prompt modes) has + been deprecated. Use `:rl-rubout " "` instead. +- The `:rl-unix-filename-rubout` command has been deprecated. Use either + `:rl-rubout "/ "` (classic readline behavior) or `:rl-filename-rubout` (using + OS path separator and ignoring spaces) instead. + +Changed +~~~~~~~ + +- Improved message if a spawned process wasn't found and a Flatpak container is + in use. +- The `:tab-move` command now takes `start` and `end` as `index` to move a tab + to the first/last position. +- Tests now automatically pick the backend (QtWebKit/QtWebEngine) based on + what's available. The `QUTE_BDD_WEBENGINE` environment variable and + `--qute-bdd-webengine` argument got replaced by `QUTE_TESTS_BACKEND` and + `--qute-backend` respectively, which can be set to either `webengine` or + `webkit`. +- Using `:tab-give` or `:tab-take` on the last tab in a window now always + closes that window, no matter what `tabs.last_close` is set to. +- Redesigned `qute://settings` (`:set`) page with buttons for options with + fixed values. +- The default `hint.selectors` now match more ARIA roles (`tab`, `checkbox`, + `menuitem`, `menuitemcheckbox` and `menuitemradio`). +- Using e.g. `:bind --mode=passthrough` now scrolls to the passthrough section + on the `qute://bindings` page. +- Clicking on a notification now tries to focus the tab where the notification + is coming from. Note this might not work properly if there is more than one + tab from the same host open. +- Improvements to userscripts: + * `qute-bitwarden` understands a new `--password-prompt-invocation`, which can + be used to specify a tool other than `rofi` to ask for a password. + * `cast` now uses `yt-dlp` if available (falling back to `youtube-dl` if not). + It also lets users override the tool to use via a `QUTE_CAST_YTDL_PROGRAM` + environment variable. + * `qute-pass` now understands a new `--prefix` argument if used in gopass + mode, which gets passed as subfolder prefix to `gopass`. + * `open_download` now supports Flatpak by using its XDG Desktop Portal. + * `open_download` now waits for the exit status of `xdg-open`, causing + qutebrowser to report any issues with it. +- The `content.headers.custom` setting now accepts empty strings as values, + resulting in an empty header being sent. +- Renamed settings: + * `qt.low_end_device_mode` -> `qt.chromium.low_end_device_mode` + * `qt.process_model` -> `qt.chromium.process_model` +- System-wide userscripts are now discovered from the correct location when + running via Flatpak (`/app/share` rather than `/usr/share`). +- Filename prompts now don't display a `..` entry in the list of files anymore. + To get back to the parent directory, either type `../` manually, or use the new + `:rl-filename-rubout` command, bound to `<Ctrl-Shift-W>` by default. + +Added +~~~~~ + +- New `input.match_counts` option which allows to turn off count matching for + more emacs-like bindings. +- New `{relative_index}` field for `tabs.title.format` (and `.pinned_format`) + which shows relative tab numbers. +- New `input.mode_override` option which allows overriding the current mode + based on the new URL when navigating or switching tabs. +- New `qt.chromium.sandboxing` setting which allows to disable Chromium's + sandboxing (mainly intended for development and testing). +- New `QUTE_TAB_INDEX` variable for userscripts, containing the index of the + current tab. +- New `editor.remove_file` setting which can be set to `False` to keep all + temporary editor files after closing the external editor. +- New `:rl-rubout` command replacing `:rl-unix-word-rubout` (and optionally + `:rl-unix-filename-rubout`), taking a delimiter as argument. +- New `:rl-filename-rubout` command, using the OS path separator and ignoring + spaces. The command also gets shown in the suggested commands for a download + filename prompt now. + +Fixed +~~~~~ + +- When `search.incremental` is disabled, searching using `/text` followed by a + backwards search via `?text` (or vice-versa) now correctly changes the search + direction. +- Elements getting a hint due to a `tabindex` now are skipped if it's set to + `-1`, reducing some false-positives. +- The audible indicator (`[A]`) now uses a 2s cooldown when the audio goes + silent, equivalent with the behavior of older QtWebEngine versions. +- With `confirm_quit` set to `downloads`, the confirmation dialog is now only + shown when closing the last window (rather than closing any window, which + would continue running that window's downloads). Unfortunately, more issues + with `confirm_quit` and multiple windows remain. +- Crash when a previous crash-log file contains non-ASCII characters (which + should never happen unless it was edited manually) +- Due to changes in Debian, an old workaround (for broken QtWebEngine patching + on Debian) caused the inferior qutebrowser error page to be displayed, when + Chromium's would have worked fine. The workaround was now dropped. +- Crash when using `<Ctrl-D>` (`:completion-item-del`) in the `:tab-focus` + list, rather than `:tab-select`. +- Work around a Qt issue causing `:spawn` to run executables from the current + directory if no system-wide executable was found. The underlying Qt bug is + tracked as https://lists.qt-project.org/pipermail/announce/2022-February/000333.html[CVE-2022-25255], + though the impact with typical qutebrowser usage is low: Normally, + qutebrowser is run from a fixed location (usually the users home directory), + and `:spawn` is not typically used with executables that don't exist. The main + security impact of this bug is in tools like text editors, which are often + executed in untrusted directories and might attempt to run auxiliary tools + automatically. +- When `:rl-rubout` or `:rl-filename-rubout` (formerly `:rl-unix-word-rubout` + and `:rl-unix-filename-rubout`) were used on a string not starting with the + given delimiter, they failed to delete the first character, which is now fixed. +- Fixes in userscripts: + * `ripbang` now works again (it got blocked due to a missing user agent and + used outdated qutebrowser commands before) + * `keepassxc` now has a properly working `--insecure` flag +- Speculative fix for an immediate crash at start with the macOS/Windows + binaries (in certain rare environments). +- Speculative fix for a qutebrowser crash when the notification daemon crashes + while showing the notification. +- Fix crash when using `:screenshot` with an invalid `--rect` argument. +- Added a site-specific quirk to make cookie dialogs on StackExchange pages + (such as Stack Overflow) work on Qt 5.12. + [[v2.4.0]] -v2.4.0 (unreleased) +v2.4.0 (2021-10-21) ------------------- +Security +~~~~~~~~ + +- **CVE-2021-41146**: Fix arbitrary command execution on Windows via URL handler + argument injection. See the + https://github.com/qutebrowser/qutebrowser/security/advisories/GHSA-vw27-fwjf-5qxm[security advisory] + for details. + +Added +~~~~~ + +- New `content.blocking.hosts.block_subdomains` setting which can be used to + disable the subdomain blocking for the hosts-based adblocker introduced in + v2.3.0. +- New `downloads.prevent_mixed_content` setting to prevent insecure + mixed-content downloads (true by default). +- New `--private` flag for `:tab-clone`, which clones a tab into a new private + window, mirroring the same flags for `:open` and `:tab-give`. + Fixed ~~~~~ - Switching tabs via mouse wheel scrolling now works properly on macOS. Set `tabs.mousewheel_switching` to false if you prefer the previous behavior. -- Crash when entering unicode surrogates into the filename prompt. -- `UnboundLocalError` in `qute-keepass` when the database couldn't be opened. +- Speculative fix for a crash when closing qutebrowser while a systray + notification is shown. Changed ~~~~~~~ @@ -34,15 +286,33 @@ Changed - When opening a file qutebrowser can't handle from a `file:///` directory listing, qutebrowser now opens it with the default application rather than displaying a download prompt. +- In Greasemonkey scripts, using "overrideMimeType" with GM_xmlhttpRequest is + now supported. +- `:hint --rapid` is now supported for the `tab` hinting target no matter what + `tabs.background` is set to, as there are various scenarios where tabs can + open in the background. +- New flags for the `qute-pass` userscript: + * `--unfiltered` to show all secrets, not just the one matching the current + URL. + * `--always-show-selection` to confirm the password to be entered even if + there's only a single match. +- In insert mode, `<Shift-Escape>` is now bound to `fake-key <Escape>` by + default, i.e., sends an Escape keypress to the website. +- Using `GM_setClipboard` in Greasemonkey scripts is now supported. [[v2.3.1]] -v2.3.1 (unreleased) +v2.3.1 (2021-07-28) ------------------- Fixed ~~~~~ +- Updated the workaround for Google Account log in claiming that this browser + isn't secure. For an equivalent workaround on older versions, run: + `:set -u https://accounts.google.com/* content.headers.user_agent "Mozilla/5.0 ({os_info}; rv:90.0) Gecko/20100101 Firefox/90.0"` - Corrupt cache file exceptions with `adblock` 0.5.0+ are now handled properly. +- Crash when entering unicode surrogates into the filename prompt. +- `UnboundLocalError` in `qute-keepass` when the database couldn't be opened. [[v2.3.0]] v2.3.0 (2021-06-28) @@ -4138,7 +4408,7 @@ v0.1.4 (2015-03-19) Changed ~~~~~~~ -* The Windows builds come with Qt 5.4.1 which has some https://lists.schokokeks.org/pipermail/qutebrowser/2015-March/000054.html[related bugfixes]. +* The Windows builds come with Qt 5.4.1 which has some https://listi.jpberlin.de/pipermail/qutebrowser/2015-March/000054.html[related bugfixes]. * Improvements to CPU usage when idle. * Ensure there's no size for `font-family` settings. * Handle URLs with double-colon as search strings. diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 1f87e9163..70447d8c5 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -24,7 +24,7 @@ several ways: * Send a mail to the mailing list at mailto:qutebrowser@lists.qutebrowser.org[] (optionally -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[subscribe] +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[subscribe] first). * Join the IRC channel link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on https://libera.chat/[Libera Chat] (https://web.libera.chat/#qutebrowser[webchat], @@ -111,9 +111,9 @@ unittests and several linters/checkers. Currently, the following tox environments are available: * Tests using https://www.pytest.org[pytest]: - - `py36`, `py37`, ...: Run pytest for python 3.6/3.7/... with the system-wide PyQt. - - `py36-pyqt512`, ..., `py36-pyqt515`: Run pytest with the given PyQt version (`py35-*` also works). - - `py36-pyqt515-cov`: Run with coverage support (other Python/PyQt versions work too). + - `py37`, `py38`, ...: Run pytest for python 3.7/3.8/... with the system-wide PyQt. + - `py37-pyqt512`, ..., `py37-pyqt515`: Run pytest with the given PyQt version (`py37-*` also works). + - `py37-pyqt515-cov`: Run with coverage support (other Python/PyQt versions work too). * `flake8`: Run various linting checks via https://pypi.python.org/pypi/flake8[flake8]. * `vulture`: Run https://pypi.python.org/pypi/vulture[vulture] to find unused code portions. @@ -168,16 +168,16 @@ Examples: ---- # run only pytest tests which failed in last run: -tox -e py35 -- --lf +tox -e py37 -- --lf # run only the end2end feature tests: -tox -e py35 -- tests/end2end/features +tox -e py37 -- tests/end2end/features # run everything with undo in the generated name, based on the scenario text -tox -e py35 -- tests/end2end/features/test_tabs_bdd.py -k undo +tox -e py37 -- tests/end2end/features/test_tabs_bdd.py -k undo # run coverage test for specific file (updates htmlcov/index.html) -tox -e py35-cov -- tests/unit/browser/test_webelem.py +tox -e py37-cov -- tests/unit/browser/test_webelem.py ---- Profiling @@ -544,11 +544,8 @@ ____ Setting up a Windows Development Environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* Install https://www.python.org/downloads/release/python-362/[Python 3.6]. +* Install https://www.python.org/downloads/release/python-3911/[Python 3.9]. * Install PyQt via `pip install PyQt5`. -* Create a file at `C:\Windows\system32\python3.bat` with the following content (adjust the path as necessary): - `@C:\Python36\python %*`. - This will make the Python 3.6 interpreter available as `python3`, which is used by various development scripts. * Install git from the https://git-scm.com/download/win[git-scm downloads page]. Try not to enable `core.autocrlf`, since that will cause `flake8` to complain a lot. Use an editor that can deal with plain line feeds instead. * Clone your favourite qutebrowser repository. diff --git a/doc/faq.asciidoc b/doc/faq.asciidoc index 503893b66..4b3596285 100644 --- a/doc/faq.asciidoc +++ b/doc/faq.asciidoc @@ -354,9 +354,9 @@ There is a total of four possible approaches to get dark websites: of the Dark Reader extension. This is mostly untested, though. How do I make copy to clipboard buttons work?:: -You can `:set content.javascript.can_access_clipboard true`, or -`:set -u some.domain content.javascript.can_access_clipboard true` if you want to limit -the setting to `some.domain`. +You can `:set content.javascript.clipboard access` to allow this globally (not +recommended!), or `:set -u some.domain content.javascript.clipboad access` if +you want to limit the setting to `some.domain`. == Troubleshooting diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index f570a3ffd..e84e36c3a 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -254,20 +254,27 @@ Clear all message notifications. [[click-element]] === click-element -Syntax: +:click-element [*--target* 'target'] [*--force-event*] 'filter' 'value'+ +Syntax: +:click-element [*--target* 'target'] [*--force-event*] [*--select-first*] 'filter' ['value']+ Click the element matching the given filter. The given filter needs to result in exactly one element, otherwise, an error is shown. ==== positional arguments -* +'filter'+: How to filter the elements. id: Get an element based on its ID. +* +'filter'+: How to filter the elements. -* +'value'+: The value to filter for. + - id: Get an element based on its ID. + - css: Filter by a CSS selector. + - position: Click the element at specified position. + Specify `value` as 'x,y'. + - focused: Click the currently focused element. + +* +'value'+: The value to filter for. Optional for 'focused' filter. ==== optional arguments * +*-t*+, +*--target*+: How to open the clicked element (normal/tab/tab-bg/window). * +*-f*+, +*--force-event*+: Force generating a fake click event. +* +*-s*+, +*--select-first*+: Select first matching element if there are multiple. [[close]] === close @@ -294,7 +301,7 @@ Cycle an option between multiple values. * +'values'+: The values to cycle through. ==== optional arguments -* +*-u*+, +*--pattern*+: The URL pattern to use. +* +*-u*+, +*--pattern*+: The link:configuring{outfilesuffix}#patterns[URL pattern] to use. * +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed. * +*-p*+, +*--print*+: Print the value after setting. @@ -391,7 +398,7 @@ This sets an option back to its default and removes it from autoconfig.yml. * +'option'+: The name of the option. ==== optional arguments -* +*-u*+, +*--pattern*+: The URL pattern to use. +* +*-u*+, +*--pattern*+: The link:configuring{outfilesuffix}#patterns[URL pattern] to use. * +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed. [[config-write-py]] @@ -679,9 +686,8 @@ Start hinting. * +*-a*+, +*--add-history*+: Whether to add the spawned or yanked link to the browsing history. * +*-r*+, +*--rapid*+: Whether to do rapid hinting. With rapid hinting, the hint mode isn't left after a hint is followed, so you can easily - open multiple links. This is only possible with targets - `tab` (with `tabs.background=true`), `tab-bg`, - `window`, `run`, `hover`, `userscript` and `spawn`. + open multiple links. Note this won't work with targets + `tab-fg`, `fill`, `delete` and `right-click`. * +*-f*+, +*--first*+: Click the first hinted element without prompting. @@ -1286,7 +1292,7 @@ If the option name ends with '?' or no value is provided, the value of the optio ==== optional arguments * +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed. * +*-p*+, +*--print*+: Print the value after setting. -* +*-u*+, +*--pattern*+: The URL pattern to use. +* +*-u*+, +*--pattern*+: The link:configuring{outfilesuffix}#patterns[URL pattern] to use. [[set-cmd-text]] === set-cmd-text @@ -1357,13 +1363,14 @@ The tab index to stop. [[tab-clone]] === tab-clone -Syntax: +:tab-clone [*--bg*] [*--window*]+ +Syntax: +:tab-clone [*--bg*] [*--window*] [*--private*]+ Duplicate the current tab. ==== optional arguments * +*-b*+, +*--bg*+: Open in a background tab. * +*-w*+, +*--window*+: Open in a new window. +* +*-p*+, +*--private*+: Open in a new private window. [[tab-close]] === tab-close @@ -1431,6 +1438,7 @@ If neither is given, move it to the first position. ==== positional arguments * +'index'+: `+` or `-` to move relative to the current tab by count, or a default of 1 space. A tab index to move to that index. + `start` and `end` to move to the start and the end. ==== count @@ -1680,13 +1688,13 @@ How many steps to zoom out. |<<rl-beginning-of-line,rl-beginning-of-line>>|Move to the start of the line. |<<rl-delete-char,rl-delete-char>>|Delete the character after the cursor. |<<rl-end-of-line,rl-end-of-line>>|Move to the end of the line. +|<<rl-filename-rubout,rl-filename-rubout>>|Delete backwards using the OS path separator as boundary. |<<rl-forward-char,rl-forward-char>>|Move forward a character. |<<rl-forward-word,rl-forward-word>>|Move forward to the end of the next word. |<<rl-kill-line,rl-kill-line>>|Remove chars from the cursor to the end of the line. |<<rl-kill-word,rl-kill-word>>|Remove chars from the cursor to the end of the current word. -|<<rl-unix-filename-rubout,rl-unix-filename-rubout>>|Remove chars from the cursor to the previous path separator. +|<<rl-rubout,rl-rubout>>|Delete backwards using the given characters as boundaries. |<<rl-unix-line-discard,rl-unix-line-discard>>|Remove chars backward from the cursor to the beginning of the line. -|<<rl-unix-word-rubout,rl-unix-word-rubout>>|Remove chars from the cursor to the beginning of the word. |<<rl-yank,rl-yank>>|Paste the most recently deleted text. |<<selection-drop,selection-drop>>|Drop selection and keep selection mode enabled. |<<selection-reverse,selection-reverse>>|Swap the stationary and moving end of the current selection. @@ -1938,6 +1946,12 @@ Move to the end of the line. This acts like readline's end-of-line. +[[rl-filename-rubout]] +=== rl-filename-rubout +Delete backwards using the OS path separator as boundary. + +For behavior that matches readline's `unix-filename-rubout` exactly, use `:rl-rubout "/ "` instead. This command uses the OS path separator (i.e. `\` on Windows) and ignores spaces. + [[rl-forward-char]] === rl-forward-char Move forward a character. @@ -1962,11 +1976,17 @@ Remove chars from the cursor to the end of the current word. This acts like readline's kill-word. -[[rl-unix-filename-rubout]] -=== rl-unix-filename-rubout -Remove chars from the cursor to the previous path separator. +[[rl-rubout]] +=== rl-rubout +Syntax: +:rl-rubout 'delim'+ + +Delete backwards using the given characters as boundaries. + +With " ", this acts like readline's `unix-word-rubout`. With " /", this acts like readline's `unix-filename-rubout`, but consider using `:rl-filename-rubout` instead: It uses the OS path separator (i.e. `\` on Windows) and ignores spaces. + +==== positional arguments +* +'delim'+: A string of characters (or a single character) until which text will be deleted. -This acts like readline's unix-filename-rubout. [[rl-unix-line-discard]] === rl-unix-line-discard @@ -1974,12 +1994,6 @@ Remove chars backward from the cursor to the beginning of the line. This acts like readline's unix-line-discard. -[[rl-unix-word-rubout]] -=== rl-unix-word-rubout -Remove chars from the cursor to the beginning of the word. - -This acts like readline's unix-word-rubout. Whitespace is used as a word delimiter. - [[rl-yank]] === rl-yank Paste the most recently deleted text. diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc index e1a57cdfb..3ecef8ecf 100644 --- a/doc/help/configuring.asciidoc +++ b/doc/help/configuring.asciidoc @@ -22,6 +22,19 @@ exists, the `autoconfig.yml` file **is not read anymore** by default. You need to <<configpy-autoconfig,load it from `config.py`>> if you want settings changed via `:set`/`:bind` to persist between restarts. +[[patterns]] +URL pattern support +------------------- + +Many settings are customizable depending on the page being visited by using URL +patterns. The link:settings{outfilesuffix}[settings documentation] marks such +settings with "This setting supports URL patterns. + +The syntax is based on Chromium's +https://developer.chrome.com/docs/extensions/mv3/match_patterns/[URL pattern syntax]. +As an extension, the scheme and path can be left off as a short-hand syntax, so +`example.com` is equivalent to `*://example.com/*`. + [[autoconfig]] Configuring qutebrowser via the user interface ---------------------------------------------- @@ -45,9 +58,10 @@ customizable. Using the link:commands{outfilesuffix}#set[`:set`] command and command completion, you can quickly set settings interactively, for example `:set tabs.position left`. -Some settings are also customizable for a given -https://developer.chrome.com/apps/match_patterns[URL pattern] by doing e.g. -`:set --pattern=*://example.com/ content.images false`. +<<patterns,URL patterns>> can be used via +`:set --pattern *://example.com/* content.images false`, or with shorthand +syntax for both argument and pattern, `:set -u example.com content.images +false`. To get more help about a setting, use e.g. `:help tabs.position`. @@ -160,8 +174,8 @@ color = config.get('colors.completion.fg') Per-domain settings ~~~~~~~~~~~~~~~~~~~ -Using `config.set`, some settings are also customizable for a given -https://developer.chrome.com/apps/match_patterns[URL pattern]: +Using `config.set` instead of the `c.` shorthand, many settings are also +customizable for a given <<patterns,URL patterns>>. [source,python] ---- @@ -377,8 +391,8 @@ import subprocess def read_xresources(prefix): props = {} - x = subprocess.run(['xrdb', '-query'], stdout=subprocess.PIPE) - lines = x.stdout.decode().split('\n') + x = subprocess.run(['xrdb', '-query'], capture_output=True, check=True, text=True) + lines = x.stdout.split('\n') for line in filter(lambda l : l.startswith(prefix), lines): prop, _, value = line.partition(':\t') props[prop] = value @@ -392,11 +406,13 @@ Pre-built colorschemes ^^^^^^^^^^^^^^^^^^^^^^ - A collection of https://github.com/chriskempson/base16[base16] color-schemes can be found in https://github.com/theova/base16-qutebrowser[base16-qutebrowser] and used with https://github.com/AuditeMarlow/base16-manager[base16-manager]. -- https://gitlab.com/jjzmajic/qutewal[Pywal integration] +- Another collection: https://github.com/leosolid/qutebrowser-themes[qutebrowser-themes] +- Pywal integration: https://gitlab.com/jjzmajic/qutewal[qutewal], https://github.com/makman12/pywalQute[pywalQute] - https://github.com/arcticicestudio/nord[Nord]: https://github.com/Linuus/nord-qutebrowser[Linuus], https://github.com/KnownAsDon/QuteBrowser-Nord-Theme[KnownAsDon] - https://github.com/dracula/qutebrowser-dracula-theme[Dracula] - https://gitlab.com/lovetocode999/selenized-qutebrowser[Selenized] -- https://github.com/morhetz/gruvbox[gruvbox]: https://github.com/The-Compiler/dotfiles/blob/master/qutebrowser/gruvbox.py[The-Compiler], https://gitlab.com/shaneyost/dots-popos-september-2020/-/blob/master/qutebrowser/config.py[Shane Yost] +- https://github.com/The-Compiler/dotfiles/blob/master/qutebrowser/gruvbox.py[gruvbox] +- https://www.opencode.net/wakellor957/qb-breath/-/blob/main/qb-breath.py[Manjaro Breath-like] Avoiding flake8 errors ^^^^^^^^^^^^^^^^^^^^^^ @@ -431,7 +447,7 @@ Various emacs/conkeror-like keybinding configs exist: - https://gitlab.com/jgkamat/qutemacs/blob/master/qutemacs.py[jgkamat] - https://gitlab.com/Kaligule/qutebrowser-emacs-config/blob/master/config.py[Kaligule] -- https://me0w.net/pit/1540882719[nm0i] +- https://web.archive.org/web/20210512185023/https://me0w.net/pit/1540882719[nm0i] - https://www.reddit.com/r/qutebrowser/comments/eh10i7/config_share_qute_with_emacs_keybindings/[jasonsun0310] It's also mostly possible to get rid of modal keybindings by setting diff --git a/doc/help/index.asciidoc b/doc/help/index.asciidoc index c7fb88c8d..127cc5d86 100644 --- a/doc/help/index.asciidoc +++ b/doc/help/index.asciidoc @@ -26,10 +26,10 @@ link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on https://libera.chat/[Libera Chat] (https://web.libera.chat/#qutebrowser[webchat], https://matrix.to/#qutebrowser:libera.chat[via Matrix]), or by writing a message to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. -There's also an https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist] +There's also an https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[announce-only mailinglist] at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also get sent to the general qutebrowser@ list). @@ -49,7 +49,7 @@ ways: * Use the built-in `:report` command or the automatic crash dialog. * Open an issue in the Github issue tracker. * Write a mail to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. Other resources diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 3c00eb083..c086ed783 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -142,6 +142,7 @@ |<<content.autoplay,content.autoplay>>|Automatically start playing `<video>` elements. |<<content.blocking.adblock.lists,content.blocking.adblock.lists>>|List of URLs to ABP-style adblocking rulesets. |<<content.blocking.enabled,content.blocking.enabled>>|Enable the ad/host blocker +|<<content.blocking.hosts.block_subdomains,content.blocking.hosts.block_subdomains>>|Block subdomains of blocked hosts. |<<content.blocking.hosts.lists,content.blocking.hosts.lists>>|List of URLs to host blocklists for the host blocker. |<<content.blocking.method,content.blocking.method>>|Which method of blocking ads should be used. |<<content.blocking.whitelist,content.blocking.whitelist>>|A list of patterns that should always be loaded, despite being blocked by the ad-/host-blocker. @@ -166,9 +167,9 @@ |<<content.hyperlink_auditing,content.hyperlink_auditing>>|Enable hyperlink auditing (`<a ping>`). |<<content.images,content.images>>|Load images automatically in web pages. |<<content.javascript.alert,content.javascript.alert>>|Show javascript alerts. -|<<content.javascript.can_access_clipboard,content.javascript.can_access_clipboard>>|Allow JavaScript to read from or write to the clipboard. |<<content.javascript.can_close_tabs,content.javascript.can_close_tabs>>|Allow JavaScript to close tabs. |<<content.javascript.can_open_tabs_automatically,content.javascript.can_open_tabs_automatically>>|Allow JavaScript to open new tabs without user interaction. +|<<content.javascript.clipboard,content.javascript.clipboard>>|Allow JavaScript to read from or write to the clipboard. |<<content.javascript.enabled,content.javascript.enabled>>|Enable JavaScript. |<<content.javascript.log,content.javascript.log>>|Log levels to use for JavaScript console logging messages. |<<content.javascript.modal_dialog,content.javascript.modal_dialog>>|Use the standard JavaScript modal dialog for `alert()` and `confirm()`. @@ -185,7 +186,7 @@ |<<content.notifications.enabled,content.notifications.enabled>>|Allow websites to show notifications. |<<content.notifications.presenter,content.notifications.presenter>>|What notification presenter to use for web notifications. |<<content.notifications.show_origin,content.notifications.show_origin>>|Whether to show the origin URL for notifications. -|<<content.pdfjs,content.pdfjs>>|Allow pdf.js to view PDF files in the browser. +|<<content.pdfjs,content.pdfjs>>|Display PDF files via PDF.js in the browser without showing a download prompt. |<<content.persistent_storage,content.persistent_storage>>|Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`. |<<content.plugins,content.plugins>>|Enable plugins in Web pages. |<<content.prefers_reduced_motion,content.prefers_reduced_motion>>|Request websites to minimize non-essentials animations and motion. @@ -208,9 +209,11 @@ |<<downloads.location.suggestion,downloads.location.suggestion>>|What to display in the download filename input. |<<downloads.open_dispatcher,downloads.open_dispatcher>>|Default program used to open downloads. |<<downloads.position,downloads.position>>|Where to show the downloaded files. +|<<downloads.prevent_mixed_content,downloads.prevent_mixed_content>>|Automatically abort insecure (HTTP) downloads originating from secure (HTTPS) pages. |<<downloads.remove_finished,downloads.remove_finished>>|Duration (in milliseconds) to wait before removing finished downloads. |<<editor.command,editor.command>>|Editor (and arguments) to use for the `edit-*` commands. |<<editor.encoding,editor.encoding>>|Encoding to use for the editor. +|<<editor.remove_file,editor.remove_file>>|Delete the temporary file upon closing the editor. |<<fileselect.folder.command,fileselect.folder.command>>|Command (and arguments) to use for selecting a single folder in forms. The command should write the selected folder path to the specified file or stdout. |<<fileselect.handler,fileselect.handler>>|Handler for selecting file(s) in forms. If `external`, then the commands specified by `fileselect.single_file.command` and `fileselect.multiple_files.command` are used to select one or multiple files respectively. |<<fileselect.multiple_files.command,fileselect.multiple_files.command>>|Command (and arguments) to use for selecting multiple files in forms. The command should write the selected file paths to the specified file or to stdout, separated by newlines. @@ -267,7 +270,9 @@ |<<input.insert_mode.leave_on_load,input.insert_mode.leave_on_load>>|Leave insert mode when starting a new page load. |<<input.insert_mode.plugins,input.insert_mode.plugins>>|Switch to insert mode when clicking flash and other plugins. |<<input.links_included_in_focus_chain,input.links_included_in_focus_chain>>|Include hyperlinks in the keyboard focus chain when tabbing. +|<<input.match_counts,input.match_counts>>|Interpret number prefixes as counts for bindings. |<<input.media_keys,input.media_keys>>|Whether the underlying Chromium should handle media keys. +|<<input.mode_override,input.mode_override>>|Mode to change to when focusing on a tab/URL changes. |<<input.mouse.back_forward_buttons,input.mouse.back_forward_buttons>>|Enable back and forward buttons on the mouse. |<<input.mouse.rocker_gestures,input.mouse.rocker_gestures>>|Enable Opera-like mouse rocker gestures. |<<input.partial_timeout,input.partial_timeout>>|Timeout (in milliseconds) for partially typed key bindings. @@ -283,13 +288,14 @@ |<<prompt.filebrowser,prompt.filebrowser>>|Show a filebrowser in download prompts. |<<prompt.radius,prompt.radius>>|Rounding radius (in pixels) for the edges of prompts. |<<qt.args,qt.args>>|Additional arguments to pass to Qt, without leading `--`. +|<<qt.chromium.low_end_device_mode,qt.chromium.low_end_device_mode>>|When to use Chromium's low-end device mode. +|<<qt.chromium.process_model,qt.chromium.process_model>>|Which Chromium process model to use. +|<<qt.chromium.sandboxing,qt.chromium.sandboxing>>|What sandboxing mechanisms in Chromium to use. |<<qt.environ,qt.environ>>|Additional environment variables to set. |<<qt.force_platform,qt.force_platform>>|Force a Qt platform to use. |<<qt.force_platformtheme,qt.force_platformtheme>>|Force a Qt platformtheme to use. |<<qt.force_software_rendering,qt.force_software_rendering>>|Force software rendering for QtWebEngine. |<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling. -|<<qt.low_end_device_mode,qt.low_end_device_mode>>|When to use Chromium's low-end device mode. -|<<qt.process_model,qt.process_model>>|Which Chromium process model to use. |<<qt.workarounds.locale,qt.workarounds.locale>>|Work around locale parsing issues in QtWebEngine 5.15.3. |<<qt.workarounds.remove_service_workers,qt.workarounds.remove_service_workers>>|Delete the QtWebEngine Service Worker directory on every start. |<<scrolling.bar,scrolling.bar>>|When/how to show the scrollbar. @@ -435,9 +441,7 @@ The following modes are available: that single keys can't be bound in this mode. * passthrough: Similar to insert mode, but passes through all keypresses - except `<Escape>` to leave the mode. It might be useful to bind - `<Escape>` to some other key in this mode if you want to be able to send - an Escape key to the website as well. Note that single keys can't be + except `<Shift+Escape>` to leave the mode. Note that single keys can't be bound in this mode. * command: Entered when pressing the `:` key in order to enter a command. @@ -520,9 +524,10 @@ Default: * +pass:[<Ctrl-Return>]+: +pass:[command-accept --rapid]+ * +pass:[<Ctrl-Shift-C>]+: +pass:[completion-item-yank --sel]+ * +pass:[<Ctrl-Shift-Tab>]+: +pass:[completion-item-focus prev-category]+ +* +pass:[<Ctrl-Shift-W>]+: +pass:[rl-filename-rubout]+ * +pass:[<Ctrl-Tab>]+: +pass:[completion-item-focus next-category]+ * +pass:[<Ctrl-U>]+: +pass:[rl-unix-line-discard]+ -* +pass:[<Ctrl-W>]+: +pass:[rl-unix-word-rubout]+ +* +pass:[<Ctrl-W>]+: +pass:[rl-rubout " "]+ * +pass:[<Ctrl-Y>]+: +pass:[rl-yank]+ * +pass:[<Down>]+: +pass:[completion-item-focus --history next]+ * +pass:[<Escape>]+: +pass:[mode-leave]+ @@ -544,6 +549,7 @@ Default: * +pass:[<Ctrl-E>]+: +pass:[edit-text]+ * +pass:[<Escape>]+: +pass:[mode-leave]+ +* +pass:[<Shift-Escape>]+: +pass:[fake-key <Escape>]+ * +pass:[<Shift-Ins>]+: +pass:[insert-text -- {primary}]+ - +pass:[normal]+: @@ -755,8 +761,9 @@ Default: * +pass:[<Ctrl-H>]+: +pass:[rl-backward-delete-char]+ * +pass:[<Ctrl-K>]+: +pass:[rl-kill-line]+ * +pass:[<Ctrl-P>]+: +pass:[prompt-open-download --pdfjs]+ +* +pass:[<Ctrl-Shift-W>]+: +pass:[rl-filename-rubout]+ * +pass:[<Ctrl-U>]+: +pass:[rl-unix-line-discard]+ -* +pass:[<Ctrl-W>]+: +pass:[rl-unix-word-rubout]+ +* +pass:[<Ctrl-W>]+: +pass:[rl-rubout " "]+ * +pass:[<Ctrl-X>]+: +pass:[prompt-open-download]+ * +pass:[<Ctrl-Y>]+: +pass:[rl-yank]+ * +pass:[<Down>]+: +pass:[prompt-item-focus next]+ @@ -1956,7 +1963,7 @@ Default: === content.autoplay Automatically start playing `<video>` elements. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. @@ -1988,7 +1995,16 @@ Default: === content.blocking.enabled Enable the ad/host blocker -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. + +Type: <<types,Bool>> + +Default: +pass:[true]+ + +[[content.blocking.hosts.block_subdomains]] +=== content.blocking.hosts.block_subdomains +Block subdomains of blocked hosts. +Note: If only a single subdomain is blocked but should be allowed, consider using `content.blocking.whitelist` instead. Type: <<types,Bool>> @@ -2054,7 +2070,7 @@ Default: empty Enable support for the HTML 5 web application cache feature. An application cache acts like an HTTP cache in some sense. For documents that use the application cache via JavaScript, the loader engine will first ask the application cache for the contents, before hitting the network. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebKit backend. @@ -2105,7 +2121,7 @@ If this setting is used with URL patterns, the pattern gets applied to the origi With QtWebEngine 5.15.0+, paths will be stripped from URLs, so URL patterns using paths will not match. With QtWebEngine 5.15.2+, subdomains are additionally stripped as well, so you will typically need to set this setting for `example.com` when the cookie is set on `somesubdomain.example.com` for it to work properly. To debug issues with this setting, start qutebrowser with `--debug --logfilter network --debug-flag log-cookies` which will show all cookies being set. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,String>> @@ -2139,7 +2155,7 @@ Default: +pass:[iso-8859-1]+ === content.desktop_capture Allow websites to share screen content. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,BoolAsk>> @@ -2155,7 +2171,7 @@ Default: +pass:[ask]+ === content.dns_prefetch Try to pre-fetch DNS entries to speed up browsing. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. @@ -2168,7 +2184,7 @@ Default: +pass:[true]+ Expand each subframe to its contents. This will flatten all the frames to become one scrollable page. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebKit backend. @@ -2197,7 +2213,7 @@ Default: +pass:[false]+ === content.geolocation Allow websites to request geolocations. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,BoolAsk>> @@ -2214,7 +2230,7 @@ Default: +pass:[ask]+ Value to send in the `Accept-Language` header. Note that the value read from JavaScript is always the global value. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,String>> @@ -2224,7 +2240,7 @@ Default: +pass:[en-US,en;q=0.9]+ === content.headers.custom Custom headers for qutebrowser HTTP requests. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Dict>> @@ -2235,7 +2251,7 @@ Default: empty Value to send in the `DNT` header. When this is set to true, qutebrowser asks websites to not track your identity. If set to null, the DNT header is not sent at all. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2244,7 +2260,7 @@ Default: +pass:[true]+ [[content.headers.referer]] === content.headers.referer When to send the Referer header. -The Referer header tells websites from which website you were coming from when visiting them. +The Referer header tells websites from which website you were coming from when visiting them. Note that with QtWebEngine, websites can override this preference by setting the `Referrer-Policy:` header, so that any websites visited from them get the full referer. No restart is needed with QtWebKit. This setting requires a restart. @@ -2283,7 +2299,7 @@ QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed to JavaScript requires a restart. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,FormatString>> @@ -2293,7 +2309,7 @@ Default: +pass:[Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, lik === content.hyperlink_auditing Enable hyperlink auditing (`<a ping>`). -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2303,7 +2319,7 @@ Default: +pass:[false]+ === content.images Load images automatically in web pages. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2317,22 +2333,11 @@ Type: <<types,Bool>> Default: +pass:[true]+ -[[content.javascript.can_access_clipboard]] -=== content.javascript.can_access_clipboard -Allow JavaScript to read from or write to the clipboard. -With QtWebEngine, writing the clipboard as response to a user interaction is always allowed. - -This setting supports URL patterns. - -Type: <<types,Bool>> - -Default: +pass:[false]+ - [[content.javascript.can_close_tabs]] === content.javascript.can_close_tabs Allow JavaScript to close tabs. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebKit backend. @@ -2344,17 +2349,34 @@ Default: +pass:[false]+ === content.javascript.can_open_tabs_automatically Allow JavaScript to open new tabs without user interaction. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> Default: +pass:[false]+ +[[content.javascript.clipboard]] +=== content.javascript.clipboard +Allow JavaScript to read from or write to the clipboard. +With QtWebEngine, writing the clipboard as response to a user interaction is always allowed. + +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. + +Type: <<types,String>> + +Valid values: + + * +none+: Disable access to clipboard. + * +access+: Allow reading from and writing to the clipboard. + * +access-paste+: Allow accessing the clipboard and pasting clipboard content. + +Default: +pass:[none]+ + [[content.javascript.enabled]] === content.javascript.enabled Enable JavaScript. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2396,7 +2418,7 @@ Default: +pass:[true]+ === content.local_content_can_access_file_urls Allow locally loaded documents to access other local URLs. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2406,7 +2428,7 @@ Default: +pass:[true]+ === content.local_content_can_access_remote_urls Allow locally loaded documents to access remote URLs. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2416,7 +2438,7 @@ Default: +pass:[false]+ === content.local_storage Enable support for HTML 5 local storage and Web SQL. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2426,7 +2448,7 @@ Default: +pass:[true]+ === content.media.audio_capture Allow websites to record audio. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. @@ -2444,7 +2466,7 @@ Default: +pass:[ask]+ === content.media.audio_video_capture Allow websites to record audio and video. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. @@ -2462,7 +2484,7 @@ Default: +pass:[ask]+ === content.media.video_capture Allow websites to record video. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. @@ -2480,7 +2502,7 @@ Default: +pass:[ask]+ === content.mouse_lock Allow websites to lock your mouse pointer. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. @@ -2499,7 +2521,7 @@ Default: +pass:[ask]+ Automatically mute tabs. Note that if the `:tab-mute` command is used, the mute status for the affected tab is now controlled manually, and this setting doesn't have any effect. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2518,7 +2540,7 @@ Default: empty === content.notifications.enabled Allow websites to show notifications. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. On QtWebEngine, this setting requires Qt 5.13 or newer. @@ -2568,7 +2590,7 @@ Whether to show the origin URL for notifications. Note that URL patterns with this setting only get matched against the origin part of the URL, so e.g. paths in patterns will never match. Note that with the `qt` presenter, origins are never shown. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. On QtWebEngine, this setting requires Qt 5.14 or newer. @@ -2580,10 +2602,10 @@ Default: +pass:[true]+ [[content.pdfjs]] === content.pdfjs -Allow pdf.js to view PDF files in the browser. -Note that the files can still be downloaded by clicking the download button in the pdf.js viewer. +Display PDF files via PDF.js in the browser without showing a download prompt. +Note that the files can still be downloaded by clicking the download button in the pdf.js viewer. With this set to `false`, the `:prompt-open-download --pdfjs` command (bound to `<Ctrl-p>` by default) can be used in the download prompt. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2593,7 +2615,7 @@ Default: +pass:[false]+ === content.persistent_storage Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. @@ -2611,7 +2633,7 @@ Default: +pass:[ask]+ === content.plugins Enable plugins in Web pages. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2637,7 +2659,7 @@ Default: +pass:[false]+ === content.print_element_backgrounds Draw the background color and images also when the page is printed. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. @@ -2657,7 +2679,7 @@ Default: +pass:[false]+ === content.proxy Proxy to use. In addition to the listed values, you can use a `socks://...` or `http://...` URL. -Note that with QtWebEngine, it will take a couple of seconds until the change is applied, if this value is changed at runtime. +Note that with QtWebEngine, it will take a couple of seconds until the change is applied, if this value is changed at runtime. Authentication for SOCKS proxies isn't supported due to Chromium limitations. Type: <<types,Proxy>> @@ -2682,7 +2704,7 @@ Default: +pass:[true]+ === content.register_protocol_handler Allow websites to register protocol handlers via `navigator.registerProtocolHandler`. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. @@ -2735,7 +2757,7 @@ Default: === content.tls.certificate_errors How to proceed on TLS certificate errors. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,String>> @@ -2752,7 +2774,7 @@ Default: +pass:[ask]+ === content.unknown_url_scheme_policy How navigation requests to URLs with unknown schemes are handled. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. @@ -2778,7 +2800,7 @@ Default: empty === content.webgl Enable WebGL. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2809,7 +2831,7 @@ Monitor load requests for cross-site scripting attempts. Suspicious scripts will be blocked and reported in the devtools JavaScript console. Note that bypasses for the XSS auditor are widely known and it can be abused for cross-site info leaks in some scenarios, see: https://www.chromium.org/developers/design-documents/xss-auditor -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -2878,6 +2900,17 @@ Valid values: Default: +pass:[top]+ +[[downloads.prevent_mixed_content]] +=== downloads.prevent_mixed_content +Automatically abort insecure (HTTP) downloads originating from secure (HTTPS) pages. +For per-domain settings, the relevant URL is the URL initiating the download, not the URL the download itself is coming from. It's not recommended to set this setting to false globally. + +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. + +Type: <<types,Bool>> + +Default: +pass:[true]+ + [[downloads.remove_finished]] === downloads.remove_finished Duration (in milliseconds) to wait before removing finished downloads. @@ -2917,6 +2950,14 @@ Type: <<types,Encoding>> Default: +pass:[utf-8]+ +[[editor.remove_file]] +=== editor.remove_file +Delete the temporary file upon closing the editor. + +Type: <<types,Bool>> + +Default: +pass:[true]+ + [[fileselect.folder.command]] === fileselect.folder.command Command (and arguments) to use for selecting a single folder in forms. The command should write the selected folder path to the specified file or stdout. @@ -3115,7 +3156,7 @@ Default: +pass:[default_size default_family]+ === fonts.web.family.cursive Font family for cursive fonts. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,FontFamily>> @@ -3125,7 +3166,7 @@ Default: empty === fonts.web.family.fantasy Font family for fantasy fonts. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,FontFamily>> @@ -3135,7 +3176,7 @@ Default: empty === fonts.web.family.fixed Font family for fixed fonts. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,FontFamily>> @@ -3145,7 +3186,7 @@ Default: empty === fonts.web.family.sans_serif Font family for sans-serif fonts. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,FontFamily>> @@ -3155,7 +3196,7 @@ Default: empty === fonts.web.family.serif Font family for serif fonts. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,FontFamily>> @@ -3165,7 +3206,7 @@ Default: empty === fonts.web.family.standard Font family for standard fonts. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,FontFamily>> @@ -3175,7 +3216,7 @@ Default: empty === fonts.web.size.default Default font size (in pixels) for regular text. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Int>> @@ -3185,7 +3226,7 @@ Default: +pass:[16]+ === fonts.web.size.default_fixed Default font size (in pixels) for fixed-pitch text. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Int>> @@ -3195,7 +3236,7 @@ Default: +pass:[13]+ === fonts.web.size.minimum Hard minimum font size (in pixels). -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Int>> @@ -3205,7 +3246,7 @@ Default: +pass:[0]+ === fonts.web.size.minimum_logical Minimum logical font size (in pixels) that is applied when zooming out. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Int>> @@ -3374,7 +3415,7 @@ Default: +pass:[true]+ === hints.selectors CSS selectors used to determine which elements on a page should have hints. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting can only be set in config.py. @@ -3401,11 +3442,17 @@ Default: * +pass:[[role="link"\]]+ * +pass:[[role="option"\]]+ * +pass:[[role="button"\]]+ +* +pass:[[role="tab"\]]+ +* +pass:[[role="checkbox"\]]+ +* +pass:[[role="menuitem"\]]+ +* +pass:[[role="menuitemcheckbox"\]]+ +* +pass:[[role="menuitemradio"\]]+ +* +pass:[[role="treeitem"\]]+ * +pass:[[ng-click\]]+ * +pass:[[ngClick\]]+ * +pass:[[data-ng-click\]]+ * +pass:[[x-ng-click\]]+ -* +pass:[[tabindex\]]+ +* +pass:[[tabindex\]:not([tabindex="-1"\])]+ - +pass:[images]+: * +pass:[img]+ @@ -3510,7 +3557,7 @@ Default: +pass:[false]+ Leave insert mode when starting a new page load. Patterns may be unreliable on this setting, and they may match the url you are navigating to, or the URL you are navigating from. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -3528,7 +3575,16 @@ Default: +pass:[false]+ === input.links_included_in_focus_chain Include hyperlinks in the keyboard focus chain when tabbing. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. + +Type: <<types,Bool>> + +Default: +pass:[true]+ + +[[input.match_counts]] +=== input.match_counts +Interpret number prefixes as counts for bindings. +This enables for vi-like bindings that can be prefixed with a number to indicate a count. Disabling it allows for emacs-like bindings where number keys are passed through (according to `input.forward_unbound_keys`) instead. Type: <<types,Bool>> @@ -3549,6 +3605,22 @@ Type: <<types,Bool>> Default: +pass:[true]+ +[[input.mode_override]] +=== input.mode_override +Mode to change to when focusing on a tab/URL changes. + +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. + +Type: <<types,String>> + +Valid values: + + * +normal+ + * +insert+ + * +passthrough+ + +Default: empty + [[input.mouse.back_forward_buttons]] === input.mouse.back_forward_buttons Enable back and forward buttons on the mouse. @@ -3579,9 +3651,9 @@ Default: +pass:[0]+ [[input.spatial_navigation]] === input.spatial_navigation Enable spatial navigation. -Spatial navigation consists in the ability to navigate between focusable elements in a Web page, such as hyperlinks and form controls, by using Left, Right, Up and Down arrow keys. For example, if the user presses the Right key, heuristics determine whether there is an element he might be trying to reach towards the right and which element he probably wants. +Spatial navigation consists in the ability to navigate between focusable elements, such as hyperlinks and form controls, on a web page by using the Left, Right, Up and Down arrow keys. For example, if a user presses the Right key, heuristics determine whether there is an element they might be trying to reach towards the right and which element they probably want. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -3717,6 +3789,73 @@ Type: <<types,List of String>> Default: empty +[[qt.chromium.low_end_device_mode]] +=== qt.chromium.low_end_device_mode +When to use Chromium's low-end device mode. +This improves the RAM usage of renderer processes, at the expense of performance. + +This setting requires a restart. + +This setting is only available with the QtWebEngine backend. + +Type: <<types,String>> + +Valid values: + + * +always+: Always use low-end device mode. + * +auto+: Decide automatically (uses low-end mode with < 1 GB available RAM). + * +never+: Never use low-end device mode. + +Default: +pass:[auto]+ + +[[qt.chromium.process_model]] +=== qt.chromium.process_model +Which Chromium process model to use. +Alternative process models use less resources, but decrease security and robustness. +See the following pages for more details: + + - https://www.chromium.org/developers/design-documents/process-models + - https://doc.qt.io/qt-5/qtwebengine-features.html#process-models + +This setting requires a restart. + +This setting is only available with the QtWebEngine backend. + +Type: <<types,String>> + +Valid values: + + * +process-per-site-instance+: Pages from separate sites are put into separate processes and separate visits to the same site are also isolated. + * +process-per-site+: Pages from separate sites are put into separate processes. Unlike Process per Site Instance, all visits to the same site will share an OS process. The benefit of this model is reduced memory consumption, because more web pages will share processes. The drawbacks include reduced security, robustness, and responsiveness. + * +single-process+: Run all tabs in a single process. This should be used for debugging purposes only, and it disables `:open --private`. + +Default: +pass:[process-per-site-instance]+ + +[[qt.chromium.sandboxing]] +=== qt.chromium.sandboxing +What sandboxing mechanisms in Chromium to use. +Chromium has various sandboxing layers, which should be enabled for normal browser usage. Mainly for testing and development, it's possible to disable individual sandboxing layers via this setting. +Open `chrome://sandbox` to see the current sandbox status. +Changing this setting is only recommended if you know what you're doing, as it **disables one of Chromium's security layers**. To avoid sandboxing being accidentally disabled persistently, this setting can only be set via `config.py`, not via `:set`. +See the Chromium documentation for more details: +- https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/linux/sandboxing.md[Linux] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox.md[Windows] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox_faq.md[FAQ (Windows-centric)] + +This setting requires a restart. + +This setting can only be set in config.py. + +This setting is only available with the QtWebEngine backend. + +Type: <<types,String>> + +Valid values: + + * +enable-all+: Enable all available sandboxing mechanisms. + * +disable-seccomp-bpf+: Disable the Seccomp BPF filter sandbox (Linux only). + * +disable-all+: Disable all sandboxing (**not recommended!**). + +Default: +pass:[enable-all]+ + [[qt.environ]] === qt.environ Additional environment variables to set. @@ -3782,48 +3921,6 @@ Type: <<types,Bool>> Default: +pass:[false]+ -[[qt.low_end_device_mode]] -=== qt.low_end_device_mode -When to use Chromium's low-end device mode. -This improves the RAM usage of renderer processes, at the expense of performance. - -This setting requires a restart. - -This setting is only available with the QtWebEngine backend. - -Type: <<types,String>> - -Valid values: - - * +always+: Always use low-end device mode. - * +auto+: Decide automatically (uses low-end mode with < 1 GB available RAM). - * +never+: Never use low-end device mode. - -Default: +pass:[auto]+ - -[[qt.process_model]] -=== qt.process_model -Which Chromium process model to use. -Alternative process models use less resources, but decrease security and robustness. -See the following pages for more details: - - - https://www.chromium.org/developers/design-documents/process-models - - https://doc.qt.io/qt-5/qtwebengine-features.html#process-models - -This setting requires a restart. - -This setting is only available with the QtWebEngine backend. - -Type: <<types,String>> - -Valid values: - - * +process-per-site-instance+: Pages from separate sites are put into separate processes and separate visits to the same site are also isolated. - * +process-per-site+: Pages from separate sites are put into separate processes. Unlike Process per Site Instance, all visits to the same site will share an OS process. The benefit of this model is reduced memory consumption, because more web pages will share processes. The drawbacks include reduced security, robustness, and responsiveness. - * +single-process+: Run all tabs in a single process. This should be used for debugging purposes only, and it disables `:open --private`. - -Default: +pass:[process-per-site-instance]+ - [[qt.workarounds.locale]] === qt.workarounds.locale Work around locale parsing issues in QtWebEngine 5.15.3. @@ -3866,7 +3963,7 @@ Default: +pass:[overlay]+ Enable smooth scrolling for web pages. Note smooth scrolling does not work with the `:scroll-px` command. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. Type: <<types,Bool>> @@ -4351,6 +4448,7 @@ The following placeholders are defined: * `{index}`: Index of this tab. * `{aligned_index}`: Index of this tab padded with spaces to have the same width. +* `{relative_index}`: Index of this tab relative to the current tab. * `{id}`: Internal tab ID of this tab. * `{scroll_pos}`: Page scroll position. * `{host}`: Host of the current web page. @@ -4595,7 +4693,7 @@ Default: +pass:[512]+ === zoom.text_only Apply the zoom factor on a frame only to the text or to all content. -This setting supports URL patterns. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebKit backend. diff --git a/doc/img/cheatsheet-big.png b/doc/img/cheatsheet-big.png Binary files differindex 75e2abb89..d47ca913f 100644 --- a/doc/img/cheatsheet-big.png +++ b/doc/img/cheatsheet-big.png diff --git a/doc/img/cheatsheet-small.png b/doc/img/cheatsheet-small.png Binary files differindex e97d63367..49e5e33df 100644 --- a/doc/img/cheatsheet-small.png +++ b/doc/img/cheatsheet-small.png diff --git a/doc/install.asciidoc b/doc/install.asciidoc index 83c332b4d..0f44e5a91 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -38,7 +38,7 @@ version (Qt 5.7, based on a Chromium from March 2016). Furthermore, it packages Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or QtWebEngine) and also comes with Python 3.5. -You should be able to install a newer Python (3.6+) using the +You should be able to install a newer Python (3.7+) using the https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or https://github.com/pyenv/pyenv[pyenv], and then proceed to <<tox,install qutebrowser in a virtualenv>>. However, this is currently untested. If you @@ -48,7 +48,7 @@ instructions! Note you'll need some basic libraries to use the virtualenv-installed PyQt: ---- -# apt install --no-install-recommends git ca-certificates python3 python3-venv asciidoc libglib2.0-0 libgl1 libfontconfig1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0 libdbus-1-3 libyaml-dev gcc python3-dev libnss3 +# apt install --no-install-recommends git ca-certificates python3 python3-venv asciidoc libglib2.0-0 libgl1 libfontconfig1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0 libdbus-1-3 libyaml-dev gcc python3-dev libnss3 libasound2 ---- // FIXME not needed anymore? @@ -69,6 +69,12 @@ from January 2018 is packaged, with QtWebEngine 5.9 based on a Chromium from Jan 2017. It's recommended to either upgrade to Ubuntu 20.04 LTS or <<tox,install qutebrowser in a virtualenv>> with a newer PyQt/Qt binary instead. +Note you'll need some basic libraries to use the virtualenv-installed PyQt: + +---- +# apt install --no-install-recommends git ca-certificates python3 python3-venv asciidoc libglib2.0-0 libgl1 libfontconfig1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0 libdbus-1-3 libyaml-dev gcc python3-dev libnss3 libasound2 +---- + Ubuntu 20.04 LTS / Linux Mint 20 (or newer) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -278,7 +284,7 @@ https://github.com/qutebrowser/qutebrowser/releases[are built] for every release. Note that you'll need to upgrade to new versions manually (subscribe to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[qutebrowser-announce mailinglist] to get notified on new releases). You can install a newer version without uninstalling the older one. @@ -335,7 +341,7 @@ files from the https://github.com/qutebrowser/qutebrowser/releases[release page]. Note that you'll need to upgrade to new versions manually (subscribe to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[qutebrowser-announce mailinglist] to get notified on new releases). The binary release ships with a QtWebEngine built without proprietary codec @@ -447,7 +453,7 @@ This installs all needed Python dependencies in a `.venv` subfolder This comes with an up-to-date Qt/PyQt including a pre-compiled QtWebEngine binary, but has a few caveats: -- Make sure your `python3` is Python 3.6 or newer, otherwise you'll get a "No +- Make sure your `python3` is Python 3.7 or newer, otherwise you'll get a "No matching distribution found" error and/or qutebrowser will not run. - It only works on 64-bit x86 systems, with other architectures you'll get the same error. diff --git a/doc/quickstart.asciidoc b/doc/quickstart.asciidoc index 2e61e442d..0c42880ab 100644 --- a/doc/quickstart.asciidoc +++ b/doc/quickstart.asciidoc @@ -33,8 +33,8 @@ image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/c `scripts/asciidoc2html.py` to generate the documentation. * Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it. * Subscribe to -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist] or -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[the announce-only mailinglist]. +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[the mailinglist] or +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[the announce-only mailinglist]. * Let me know what features you are missing or things that need (even small!) improvements. @@ -52,7 +52,7 @@ or https://matrix.to/#qutebrowser:libera.chat[via Matrix]) * On Reddit: https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] * Via https://github.com/qutebrowser/qutebrowser/discussions[GitHub Discussions] * Using the mailinglist: mailto:qutebrowser@lists.qutebrowser.org[] -(https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[subscribe]) +(https://listi.jpberlin.de/mailman/listinfo/qutebrowser[subscribe]) Donating -------- diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index 8db231add..e83a4da0b 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -10,14 +10,14 @@ :homepage: https://www.qutebrowser.org/ == NAME -qutebrowser - a keyboard-driven, vim-like browser based on PyQt5. +qutebrowser - a keyboard-driven, vim-like browser based on Python and Qt. == SYNOPSIS *qutebrowser* ['-OPTION' ['...']] [':COMMAND' ['...']] ['URL' ['...']] == DESCRIPTION qutebrowser is a keyboard-focused browser with a minimal GUI. It's based -on Python and Qt5 and is free software, licensed under the GPL. +on Python and Qt and is free software, licensed under the GPL. It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl. @@ -34,7 +34,7 @@ show it. *'URL'*:: URLs to open on startup (empty as a window separator). -=== optional arguments +=== options *-h*, *--help*:: show this help message and exit @@ -65,6 +65,9 @@ show it. *--desktop-file-name* 'DESKTOP_FILE_NAME':: Set the base name of the desktop entry for this application. Used to set the app_id under Wayland. See https://doc.qt.io/qt-5/qguiapplication.html#desktopFileName-prop +*--untrusted-args*:: + Mark all following arguments as untrusted, which enforces that they are URLs/search terms (and not flags or commands) + === debug arguments *-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}':: Override the configured console loglevel @@ -127,7 +130,7 @@ If you found a bug, use the built-in ':report' command to create a bug report with all information needed. If you prefer, you can also write to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[] instead. For security bugs, please contact me directly at me@the-compiler.org, GPG ID @@ -149,9 +152,9 @@ this program. If not, see <https://www.gnu.org/licenses/>. == RESOURCES * Website: https://www.qutebrowser.org/ * Mailinglist: mailto:qutebrowser@lists.qutebrowser.org[] / -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser +https://listi.jpberlin.de/mailman/listinfo/qutebrowser * Announce-only mailinglist: mailto:qutebrowser-announce@lists.qutebrowser.org[] / -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce * IRC: link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on https://libera.chat/[Libera Chat] (https://web.libera.chat/#qutebrowser[webchat], https://matrix.to/#qutebrowser:libera.chat[via Matrix]) diff --git a/doc/stacktrace.asciidoc b/doc/stacktrace.asciidoc index 3d95aa25e..87410bd48 100644 --- a/doc/stacktrace.asciidoc +++ b/doc/stacktrace.asciidoc @@ -68,55 +68,13 @@ Then install the needed debuginfo packages: Archlinux ^^^^^^^^^ -For Archlinux, no debug information is provided. You can either compile Qt -yourself (which will take a few hours even on a modern machine) or use -debugging symbols compiled/packaged by me (x86_64 only). - -To install my pre-built packages -++++++++++++++++++++++++++++++++ - -First download and sign the key: - ----- -# pacman-key -r 0xD6A1C70FE80A0C82 -$ pacman-key -f 0xD6A1C70FE80A0C82 - Key fingerprint = 14AF EC28 70C6 4863 C5C7 ACCB D6A1 C70F E80A 0C82 -# pacman-key --lsign-key 0xD6A1C70FE80A0C82 ----- - -Then edit your `/etc/pacman.conf` to add the repository to the bottom: +For Archlinux, debug information is provided via their https://wiki.archlinux.org/title/Debuginfod[Debuginfod instance]. To use it, set the following in your environment: ---- -[qt-debug] -Server = https://qutebrowser.org/qt-debug/$arch +DEBUGINFOD_URLS="https://debuginfod.archlinux.org/" ---- -Then install the packages: - ----- -# pacman -Suy python-pyqt5-debug qt5-base-debug qt5-webkit-debug qt5-webengine-debug ----- - -The `-debug` packages conflict with the non-debug variants - it's safe to -remove them. - -To compile by yourself -++++++++++++++++++++++ - -Note that building Qt will likely take multiple hours, even on a recent system. -I'd also expect it to take around 6 GB of RAM and 30 GB of disk space for a -successful compile run. - ----- -$ git clone https://github.com/qutebrowser/qt-debug-pkgbuild.git -$ cd qt-debug-pkgbuild -$ export DEBUG_CFLAGS='-ggdb3 -fvar-tracking-assignments -Og' -$ export DEBUG_CXXFLAGS='-ggdb3 -fvar-tracking-assignments -Og' -$ cd qt5 -$ makepkg -si --pkg qt5-base-debug,qt5-webkit-debug,qt5-webengine-debug -$ cd ../pyqt5 -$ makepkg -si --pkg python-pyqt5-debug ----- +(Until early 2021, there was a custom [`qt-debug` repository](https://github.com/qutebrowser/qt-debug-pkgbuild). This is now archived.) Getting the stack trace ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/userscripts.asciidoc b/doc/userscripts.asciidoc index 5ff358ee0..4539a0ca8 100644 --- a/doc/userscripts.asciidoc +++ b/doc/userscripts.asciidoc @@ -56,6 +56,7 @@ In `command` mode: - `QUTE_TITLE`: The title of the current page. - `QUTE_SELECTED_TEXT`: The text currently selected on the page. - `QUTE_COUNT`: The `count` from the spawn command running the userscript. +- `QUTE_TAB_INDEX`: The current tab's index. In `hints` mode: diff --git a/misc/Makefile b/misc/Makefile index b916a20d5..62294ba61 100644 --- a/misc/Makefile +++ b/misc/Makefile @@ -24,9 +24,9 @@ install: man "$(DESTDIR)$(MANDIR)/man1/qutebrowser.1" install -Dm644 misc/org.qutebrowser.qutebrowser.desktop \ "$(DESTDIR)$(DATADIR)/applications/org.qutebrowser.qutebrowser.desktop" - $(foreach i,$(ICONSIZES),install -Dm644 "icons/qutebrowser-$(i)x$(i).png" \ + $(foreach i,$(ICONSIZES),install -Dm644 "qutebrowser/icons/qutebrowser-$(i)x$(i).png" \ "$(DESTDIR)$(DATADIR)/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";) - install -Dm644 icons/qutebrowser.svg \ + install -Dm644 qutebrowser/icons/qutebrowser.svg \ "$(DESTDIR)$(DATADIR)/icons/hicolor/scalable/apps/qutebrowser.svg" install -Dm755 -t "$(DESTDIR)$(DATADIR)/qutebrowser/userscripts/" \ $(filter-out misc/userscripts/__pycache__,$(wildcard misc/userscripts/*)) diff --git a/misc/nsis/install.nsh b/misc/nsis/install.nsh index f29a0a9a8..8233ab5f0 100755 --- a/misc/nsis/install.nsh +++ b/misc/nsis/install.nsh @@ -351,13 +351,12 @@ Section "Register with Windows" SectionWindowsRegister !insertmacro UpdateRegDWORD SHCTX "SOFTWARE\Classes\$2" "EditFlags" 0x00000002 !insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2\DefaultIcon" "" "$1,0" !insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2\shell" "" "open" - !insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2\shell\open\command" "" "$\"$1$\" $\"%1$\"" + !insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2\shell\open\command" "" "$\"$1$\" --untrusted-args $\"%1$\"" !insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2\shell\open\ddeexec" "" "" StrCmp $2 "${PRODUCT_NAME}HTML" 0 +4 StrCpy $2 "${PRODUCT_NAME}URL" StrCpy $3 "${PRODUCT_NAME} URL" Goto WriteRegHandler - !insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2" "URL Protocol" "" ${endif} SectionEnd @@ -543,8 +542,16 @@ Function PageInstallModeChangeMode FunctionEnd Function PageComponentsPre - GetDlgItem $0 $HWNDPARENT 1 - SendMessage $0 ${BCM_SETSHIELD} 0 0 ; hide SHIELD (Windows Vista and above) + SendMessage $mui.Button.Next ${BCM_SETSHIELD} 0 0 + StrCmpS $HasCurrentModeInstallation 0 +9 + IfFileExists "$DESKTOP\${PRODUCT_NAME}.lnk" +4 + SectionGetFlags ${SectionDesktopIcon} $1 + IntOp $1 $1 & 0xFFFFFFFE + SectionSetFlags ${SectionDesktopIcon} $1 + IfFileExists "$STARTMENU\${PRODUCT_NAME}.lnk" +4 + SectionGetFlags ${SectionStartMenuIcon} $1 + IntOp $1 $1 & 0xFFFFFFFE + SectionSetFlags ${SectionStartMenuIcon} $1 FunctionEnd Function PageDirectoryPre diff --git a/misc/nsis/qutebrowser.nsi b/misc/nsis/qutebrowser.nsi index 51f3b623d..7623d8cb2 100755 --- a/misc/nsis/qutebrowser.nsi +++ b/misc/nsis/qutebrowser.nsi @@ -43,6 +43,9 @@ ShowUninstDetails hide !addplugindir /x86-unicode ".\plugins\x86-unicode"
!addincludedir ".\include"
+!define MUI_BGCOLOR "SYSCLR:Window"
+!define MUI_TEXTCOLOR "SYSCLR:WindowText"
+
!include MUI2.nsh
!include NsisMultiUser.nsh
!include StdUtils.nsh
@@ -57,7 +60,7 @@ ShowUninstDetails hide !define URL_UPDATE "https://qutebrowser.org/doc/install.html"
!define HELP_LINK "https://qutebrowser.org/doc/help/"
!define CONTACT "mail@qutebrowser.org"
-!define COMMENTS "A keyboard-driven, vim-like browser based on PyQt5."
+!define COMMENTS "A keyboard-driven, vim-like browser based on Python and Qt."
!define LANGID "1033" ; U.S. English
!define MIN_WIN_VER "8"
!define SETUP_MUTEX "${PRODUCT_NAME} Setup Mutex" ; do not change this between program versions!
diff --git a/misc/org.qutebrowser.qutebrowser.appdata.xml b/misc/org.qutebrowser.qutebrowser.appdata.xml index 49bc09786..f506bd5a8 100644 --- a/misc/org.qutebrowser.qutebrowser.appdata.xml +++ b/misc/org.qutebrowser.qutebrowser.appdata.xml @@ -44,6 +44,10 @@ </content_rating> <releases> <!-- Add new releases here --> +<release version="2.5.1" date="2022-05-26"/> +<release version="2.5.0" date="2022-04-01"/> +<release version="2.4.0" date="2021-10-21"/> +<release version="2.3.1" date="2021-07-28"/> <release version="2.3.0" date="2021-06-28"/> <release version="2.2.3" date="2021-06-01"/> <release version="2.2.2" date="2021-05-20"/> diff --git a/misc/org.qutebrowser.qutebrowser.desktop b/misc/org.qutebrowser.qutebrowser.desktop index 52144b3c5..741a00371 100644 --- a/misc/org.qutebrowser.qutebrowser.desktop +++ b/misc/org.qutebrowser.qutebrowser.desktop @@ -39,13 +39,13 @@ GenericName[ta]=இணைய உலாவி GenericName[th]=เว็บเบราว์เซอร์ GenericName[tr]=Web Tarayıcı GenericName[uk]=Навігатор Тенет瀏覽器 -Comment=A keyboard-driven, vim-like browser based on PyQt5 -Comment[de]=Ein Tastatur-gesteuerter, vim-ähnlicher Browser basierend auf PyQt5 -Comment[it]= Un browser web vim-like utilizzabile da tastiera basato su PyQt5 +Comment=A keyboard-driven, vim-like browser based on Python and Qt +Comment[de]=Ein Tastatur-gesteuerter, vim-ähnlicher Browser basierend auf Python und Qt +Comment[it]= Un browser web vim-like utilizzabile da tastiera basato su Python e Qt Icon=qutebrowser Type=Application Categories=Network;WebBrowser; -Exec=qutebrowser %u +Exec=qutebrowser --untrusted-args %u Terminal=false StartupNotify=true MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute; diff --git a/misc/qutebrowser.rcc b/misc/qutebrowser.rcc deleted file mode 100644 index 2f73b3726..000000000 --- a/misc/qutebrowser.rcc +++ /dev/null @@ -1,13 +0,0 @@ -<!DOCTYPE RCC><RCC version="1.0"> -<qresource> - <file>icons/qutebrowser-16x16.png</file> - <file>icons/qutebrowser-24x24.png</file> - <file>icons/qutebrowser-32x32.png</file> - <file>icons/qutebrowser-48x48.png</file> - <file>icons/qutebrowser-64x64.png</file> - <file>icons/qutebrowser-96x96.png</file> - <file>icons/qutebrowser-128x128.png</file> - <file>icons/qutebrowser-256x256.png</file> - <file>icons/qutebrowser-512x512.png</file> -</qresource> -</RCC> diff --git a/misc/qutebrowser.spec b/misc/qutebrowser.spec index ffb17d371..60729266e 100644 --- a/misc/qutebrowser.spec +++ b/misc/qutebrowser.spec @@ -15,6 +15,7 @@ def get_data_files(): data_files = [ ('../qutebrowser/html', 'html'), ('../qutebrowser/img', 'img'), + ('../qutebrowser/icons', 'icons'), ('../qutebrowser/javascript', 'javascript'), ('../qutebrowser/html/doc', 'html/doc'), ('../qutebrowser/git-commit-id', '.'), @@ -40,9 +41,9 @@ setupcommon.write_git_file() if os.name == 'nt': - icon = '../icons/qutebrowser.ico' + icon = '../qutebrowser/icons/qutebrowser.ico' elif sys.platform == 'darwin': - icon = '../icons/qutebrowser.icns' + icon = '../qutebrowser/icons/qutebrowser.icns' else: icon = None diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 8b7067709..c51ef3d0e 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -build==0.5.1 -check-manifest==0.46 -packaging==21.0 -pep517==0.10.0 -pyparsing==2.4.7 -toml==0.10.2 +build==0.8.0 +check-manifest==0.48 +packaging==21.3 +pep517==0.12.0 +pyparsing==3.0.9 +tomli==2.0.1 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 2963888b6..e4e768353 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -1,26 +1,45 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py +bleach==5.0.0 +build==0.8.0 bump2version==1.0.1 -certifi==2021.5.30 -cffi==1.14.6 -chardet==4.0.0 -cryptography==3.4.7 -Deprecated==1.2.12 -github3.py==2.0.0 -hunter==3.3.8 -idna==2.10 -jwcrypto==0.9.1 +certifi==2022.5.18.1 +cffi==1.15.0 +charset-normalizer==2.0.12 +commonmark==0.9.1 +cryptography==37.0.2 +docutils==0.18.1 +github3.py==3.2.0 +hunter==3.4.3 +idna==3.3 +importlib-metadata==4.11.4 +jeepney==0.8.0 +keyring==23.6.0 manhole==1.8.0 -packaging==21.0 -pycparser==2.20 -Pympler==0.9 -pyparsing==2.4.7 -PyQt-builder==1.10.3 -python-dateutil==2.8.1 -requests==2.25.1 -sip==6.1.1 +packaging==21.3 +pep517==0.12.0 +pkginfo==1.8.3 +ply==3.11 +pycparser==2.21 +Pygments==2.12.0 +PyJWT==2.4.0 +Pympler==1.0.1 +pyparsing==3.0.9 +PyQt-builder==1.12.2 +python-dateutil==2.8.2 +readme-renderer==35.0 +requests==2.28.0 +requests-toolbelt==0.9.1 +rfc3986==2.0.0 +rich==12.4.4 +SecretStorage==3.3.2 +sip==6.6.1 six==1.16.0 toml==0.10.2 -uritemplate==3.0.1 -# urllib3==1.26.6 -wrapt==1.12.1 +tomli==2.0.1 +twine==4.0.1 +typing_extensions==4.2.0 +uritemplate==4.1.1 +# urllib3==1.26.9 +webencodings==0.5.1 +zipp==3.8.0 diff --git a/misc/requirements/requirements-dev.txt-raw b/misc/requirements/requirements-dev.txt-raw index fd840bab1..261f4459f 100644 --- a/misc/requirements/requirements-dev.txt-raw +++ b/misc/requirements/requirements-dev.txt-raw @@ -4,6 +4,8 @@ github3.py bump2version requests pyqt-builder +build +twine # Already included via test requirements #@ ignore: urllib3 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index cc041b538..217089191 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -1,24 +1,25 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -attrs==21.2.0 -flake8==3.9.2 -flake8-bugbear==21.4.3 +attrs==21.4.0 +flake8==4.0.1 +flake8-bugbear==22.4.25 flake8-builtins==1.5.3 -flake8-comprehensions==3.5.0 +flake8-comprehensions==3.10.0 flake8-copyright==0.2.2 -flake8-debugger==4.0.0 +flake8-debugger==4.1.2 flake8-deprecated==1.3 flake8-docstrings==1.6.0 flake8-future-import==0.4.6 flake8-mock==0.3 -flake8-polyfill==1.0.2 +flake8-plugin-utils==1.3.2 +flake8-pytest-style==1.6.0 flake8-string-format==0.3.0 -flake8-tidy-imports==4.3.0 +flake8-tidy-imports==4.8.0 flake8-tuple==0.4.1 mccabe==0.6.1 -pep8-naming==0.12.0 -pycodestyle==2.7.0 +pep8-naming==0.13.0 +pycodestyle==2.8.0 pydocstyle==6.1.1 -pyflakes==2.3.1 +pyflakes==2.4.0 six==1.16.0 -snowballstemmer==2.1.0 +snowballstemmer==2.2.0 diff --git a/misc/requirements/requirements-flake8.txt-raw b/misc/requirements/requirements-flake8.txt-raw index 1bdca6974..59908897c 100644 --- a/misc/requirements/requirements-flake8.txt-raw +++ b/misc/requirements/requirements-flake8.txt-raw @@ -11,6 +11,7 @@ flake8-mock flake8-string-format flake8-tidy-imports flake8-tuple +flake8-pytest-style pep8-naming pydocstyle pyflakes diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index e8b09d628..a4b555cf3 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -1,21 +1,18 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py chardet==4.0.0 -diff-cover==6.1.1 -importlib-metadata==4.6.1 -importlib-resources==5.2.0 -inflect==5.3.0 -Jinja2==3.0.1 -jinja2-pluralize==0.3.0 -lxml==4.6.3 -MarkupSafe==2.0.1 -mypy==0.910 +diff-cover==6.5.0 +importlib-metadata==4.11.4 +importlib-resources==5.7.1 +Jinja2==3.1.2 +lxml==4.9.0 +MarkupSafe==2.1.1 +mypy==0.961 mypy-extensions==0.4.3 -pluggy==0.13.1 -Pygments==2.9.0 -PyQt5-stubs==5.15.2.0 -toml==0.10.2 -types-dataclasses==0.1.5 -types-PyYAML==5.4.3 -typing-extensions==3.10.0.0 -zipp==3.5.0 +pluggy==1.0.0 +Pygments==2.12.0 +PyQt5-stubs==5.15.6.0 +tomli==2.0.1 +types-PyYAML==6.0.8 +typing_extensions==4.2.0 +zipp==3.8.0 diff --git a/misc/requirements/requirements-mypy.txt-raw b/misc/requirements/requirements-mypy.txt-raw index 4baeec11f..dd00d3219 100644 --- a/misc/requirements/requirements-mypy.txt-raw +++ b/misc/requirements/requirements-mypy.txt-raw @@ -3,7 +3,6 @@ lxml # For HTML reports diff-cover PyQt5-stubs -types-dataclasses types-PyYAML # So stubs are available even on newer Python versions diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt deleted file mode 100644 index aba943496..000000000 --- a/misc/requirements/requirements-pip.txt +++ /dev/null @@ -1,8 +0,0 @@ -# This file is automatically generated by scripts/dev/recompile_requirements.py - -appdirs==1.4.4 -packaging==20.4 -pyparsing==2.4.7 -setuptools==47.3.1 -six==1.15.0 -wheel==0.34.2 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index 86d73655d..35e65b6da 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -altgraph==0.17 -pyinstaller==4.3 -pyinstaller-hooks-contrib==2021.2 +altgraph==0.17.2 +pyinstaller==5.1 +pyinstaller-hooks-contrib==2022.7 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index f0a1c2cb4..38231fa12 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,26 +1,30 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==2.3.3 # rq.filter: < 2.4 -certifi==2021.5.30 -cffi==1.14.6 -chardet==4.0.0 -cryptography==3.4.7 -Deprecated==1.2.12 +astroid==2.11.5 +certifi==2022.5.18.1 +cffi==1.15.0 +charset-normalizer==2.0.12 +cryptography==37.0.2 +dill==0.3.5.1 future==0.18.2 -github3.py==2.0.0 -idna==2.10 -isort==4.3.21 -jwcrypto==0.9.1 -lazy-object-proxy==1.4.3 -mccabe==0.6.1 -pefile==2021.5.24 -pycparser==2.20 -pylint==2.4.4 # rq.filter: < 2.5 -python-dateutil==2.8.1 +github3.py==3.2.0 +idna==3.3 +isort==5.10.1 +lazy-object-proxy==1.7.1 +mccabe==0.7.0 +pefile==2022.5.30 +platformdirs==2.5.2 +pycparser==2.21 +PyJWT==2.4.0 +pylint==2.14.1 +python-dateutil==2.8.2 ./scripts/dev/pylint_checkers -requests==2.25.1 +requests==2.28.0 six==1.16.0 -typed-ast==1.4.3 ; python_version<"3.8" -uritemplate==3.0.1 -# urllib3==1.26.6 -wrapt==1.11.2 +tomli==2.0.1 +tomlkit==0.11.0 +typed-ast==1.5.4 ; python_version<"3.8" +typing_extensions==4.2.0 +uritemplate==4.1.1 +# urllib3==1.26.9 +wrapt==1.14.1 diff --git a/misc/requirements/requirements-pylint.txt-raw b/misc/requirements/requirements-pylint.txt-raw index 08d340665..54e12a02a 100644 --- a/misc/requirements/requirements-pylint.txt-raw +++ b/misc/requirements/requirements-pylint.txt-raw @@ -1,4 +1,4 @@ -pylint<2.5 +pylint ./scripts/dev/pylint_checkers requests github3.py @@ -7,8 +7,6 @@ pefile # fix qute-pylint location #@ replace: qute-pylint.* ./scripts/dev/pylint_checkers #@ markers: typed-ast python_version<"3.8" -#@ filter: pylint < 2.5 -#@ filter: astroid < 2.4 # Already included via test requirements #@ ignore: urllib3 diff --git a/misc/requirements/requirements-pyqt-5.12.txt b/misc/requirements/requirements-pyqt-5.12.txt index 890306127..499f420e5 100644 --- a/misc/requirements/requirements-pyqt-5.12.txt +++ b/misc/requirements/requirements-pyqt-5.12.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt5==5.12.3 # rq.filter: < 5.13 -PyQt5-sip==12.9.0 +PyQt5-sip==12.10.1 PyQtWebEngine==5.12.1 # rq.filter: < 5.13 diff --git a/misc/requirements/requirements-pyqt-5.13.txt b/misc/requirements/requirements-pyqt-5.13.txt index 5f4da4758..0fb28e312 100644 --- a/misc/requirements/requirements-pyqt-5.13.txt +++ b/misc/requirements/requirements-pyqt-5.13.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt5==5.13.2 # rq.filter: < 5.14 -PyQt5-sip==12.9.0 +PyQt5-sip==12.10.1 PyQtWebEngine==5.13.2 # rq.filter: < 5.14 diff --git a/misc/requirements/requirements-pyqt-5.14.txt b/misc/requirements/requirements-pyqt-5.14.txt index 9ce643666..953b3ded6 100644 --- a/misc/requirements/requirements-pyqt-5.14.txt +++ b/misc/requirements/requirements-pyqt-5.14.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt5==5.14.2 # rq.filter: < 5.15 -PyQt5-sip==12.9.0 +PyQt5-sip==12.10.1 PyQtWebEngine==5.14.0 # rq.filter: < 5.15 diff --git a/misc/requirements/requirements-pyqt-5.15.0.txt b/misc/requirements/requirements-pyqt-5.15.0.txt index b111a93f3..703a8b248 100644 --- a/misc/requirements/requirements-pyqt-5.15.0.txt +++ b/misc/requirements/requirements-pyqt-5.15.0.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt5==5.15.0 # rq.filter: == 5.15.0 -PyQt5-sip==12.9.0 +PyQt5-sip==12.10.1 PyQtWebEngine==5.15.0 # rq.filter: == 5.15.0 diff --git a/misc/requirements/requirements-pyqt-5.15.txt b/misc/requirements/requirements-pyqt-5.15.txt index 8b7a53c44..daa7e7136 100644 --- a/misc/requirements/requirements-pyqt-5.15.txt +++ b/misc/requirements/requirements-pyqt-5.15.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.15.4 # rq.filter: < 5.16 +PyQt5==5.15.6 # rq.filter: < 5.16 PyQt5-Qt5==5.15.2 -PyQt5-sip==12.9.0 -PyQtWebEngine==5.15.4 # rq.filter: < 5.16 +PyQt5-sip==12.10.1 +PyQtWebEngine==5.15.5 # rq.filter: < 5.16 PyQtWebEngine-Qt5==5.15.2 diff --git a/misc/requirements/requirements-pyqt-pyinstaller.txt b/misc/requirements/requirements-pyqt-pyinstaller.txt deleted file mode 100644 index 678a1d7ea..000000000 --- a/misc/requirements/requirements-pyqt-pyinstaller.txt +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically generated by scripts/dev/recompile_requirements.py - -PyQt5==5.15.3 -PyQt5-Qt==5.15.2 -PyQt5-sip==12.9.0 -PyQtWebEngine==5.15.3 -PyQtWebEngine-Qt==5.15.2 diff --git a/misc/requirements/requirements-pyqt-pyinstaller.txt-raw b/misc/requirements/requirements-pyqt-pyinstaller.txt-raw deleted file mode 100644 index 89b5644da..000000000 --- a/misc/requirements/requirements-pyqt-pyinstaller.txt-raw +++ /dev/null @@ -1,2 +0,0 @@ -PyQt5==5.15.3 -PyQtWebEngine==5.15.3 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 75ef27bf4..1641b4fcd 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.15.4 +PyQt5==5.15.6 PyQt5-Qt5==5.15.2 -PyQt5-sip==12.9.0 -PyQtWebEngine==5.15.4 +PyQt5-sip==12.10.1 +PyQtWebEngine==5.15.5 PyQtWebEngine-Qt5==5.15.2 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index a83804b9e..382418dd9 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -1,10 +1,15 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -certifi==2021.5.30 -chardet==4.0.0 -docutils==0.17.1 -idna==2.10 -Pygments==2.9.0 -pyroma==3.2 -requests==2.25.1 -urllib3==1.26.6 +build==0.8.0 +certifi==2022.5.18.1 +charset-normalizer==2.0.12 +docutils==0.18.1 +idna==3.3 +packaging==21.3 +pep517==0.12.0 +Pygments==2.12.0 +pyparsing==3.0.9 +pyroma==4.0 +requests==2.28.0 +tomli==2.0.1 +urllib3==1.26.9 diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw index e07ea992a..2025280fc 100644 --- a/misc/requirements/requirements-qutebrowser.txt-raw +++ b/misc/requirements/requirements-qutebrowser.txt-raw @@ -3,7 +3,6 @@ PyYAML ## stdlib backports importlib-resources -dataclasses ## Optional dependencies Pygments # For :view-source --pygments or on QtWebKit @@ -14,7 +13,6 @@ adblock # Improved adblocking importlib-metadata # Determining PyQt version typing_extensions # from importlib-metadata -#@ markers: importlib-resources python_version<"3.9" -#@ markers: importlib-metadata python_version<"3.8" +#@ markers: importlib-resources python_version=="3.7.*" or python_version=="3.8.*" +#@ markers: importlib-metadata python_version=="3.7.*" #@ markers: typing_extensions python_version<"3.8" -#@ markers: dataclasses python_version<"3.7" diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index f5a7cb3ca..f100b6dc0 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -1,25 +1,27 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py alabaster==0.7.12 -Babel==2.9.1 -certifi==2021.5.30 -chardet==4.0.0 -docutils==0.17.1 -idna==2.10 -imagesize==1.2.0 -Jinja2==3.0.1 -MarkupSafe==2.0.1 -packaging==21.0 -Pygments==2.9.0 -pyparsing==2.4.7 -pytz==2021.1 -requests==2.25.1 -snowballstemmer==2.1.0 -Sphinx==4.1.0 +Babel==2.10.1 +certifi==2022.5.18.1 +charset-normalizer==2.0.12 +docutils==0.18.1 +idna==3.3 +imagesize==1.3.0 +importlib-metadata==4.11.4 +Jinja2==3.1.2 +MarkupSafe==2.1.1 +packaging==21.3 +Pygments==2.12.0 +pyparsing==3.0.9 +pytz==2022.1 +requests==2.28.0 +snowballstemmer==2.2.0 +Sphinx==5.0.1 sphinxcontrib-applehelp==1.0.2 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.0 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 -urllib3==1.26.6 +urllib3==1.26.9 +zipp==3.8.0 diff --git a/misc/requirements/requirements-tests-bleeding.txt b/misc/requirements/requirements-tests-bleeding.txt index 5fbb05f05..fca7328f8 100644 --- a/misc/requirements/requirements-tests-bleeding.txt +++ b/misc/requirements/requirements-tests-bleeding.txt @@ -7,7 +7,9 @@ git+https://github.com/pallets/flask.git git+https://github.com/pallets/werkzeug.git # transitive dep, but needed to work git+https://github.com/HypothesisWorks/hypothesis.git#subdirectory=hypothesis-python git+https://github.com/pytest-dev/pytest.git -git+https://github.com/pytest-dev/pytest-bdd.git +# Problematic: https://github.com/pytest-dev/pytest-bdd/issues/447 +# git+https://github.com/pytest-dev/pytest-bdd.git +pytest-bdd<5 git+https://github.com/ionelmc/pytest-benchmark.git git+https://github.com/pytest-dev/pytest-instafail.git git+https://github.com/pytest-dev/pytest-mock.git @@ -22,8 +24,6 @@ git+https://github.com/pytest-dev/pytest-cov.git git+https://github.com/The-Compiler/pytest-xvfb.git git+https://github.com/pytest-dev/pytest-xdist.git git+https://github.com/john-kurkowski/tldextract -# https://github.com/hjwp/pytest-icdiff/pull/20 -# git+https://github.com/hjwp/pytest-icdiff.git # Problematic: needs rust (and some time to build) # git+https://github.com/ArniDagur/python-adblock.git @@ -32,6 +32,7 @@ adblock ## qutebrowser dependencies git+https://github.com/pallets/jinja.git -git+https://github.com/yaml/pyyaml.git +# Problematic: https://github.com/yaml/pyyaml/issues/601 +PyYAML git+https://github.com/tartley/colorama.git git+https://github.com/pyparsing/pyparsing.git diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 0d4db4579..3e9f3233d 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,59 +1,59 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -attrs==21.2.0 -beautifulsoup4==4.9.3 -certifi==2021.5.30 -chardet==4.0.0 -cheroot==8.5.2 -click==8.0.1 -coverage==5.5 -EasyProcess==0.3 +attrs==21.4.0 +beautifulsoup4==4.11.1 +certifi==2022.5.18.1 +charset-normalizer==2.0.12 +cheroot==8.6.0 +click==8.1.3 +coverage==6.4.1 +exceptiongroup==1.0.0rc8 execnet==1.9.0 -filelock==3.0.12 -Flask==2.0.1 +filelock==3.7.1 +Flask==2.1.2 glob2==0.7 -hunter==3.3.8 -hypothesis==6.14.2 -icdiff==1.9.1 -idna==2.10 +hunter==3.4.3 +hypothesis==6.47.2 +idna==3.3 +importlib-metadata==4.11.4 iniconfig==1.1.1 -itsdangerous==2.0.1 -jaraco.functools==3.3.0 -# Jinja2==3.0.1 -Mako==1.1.4 +itsdangerous==2.1.2 +jaraco.functools==3.5.0 +# Jinja2==3.1.2 +Mako==1.2.0 manhole==1.8.0 -# MarkupSafe==2.0.1 -more-itertools==8.8.0 -packaging==21.0 +# MarkupSafe==2.1.1 +more-itertools==8.13.0 +packaging==21.3 parse==1.19.0 -parse-type==0.5.2 -pluggy==0.13.1 -pprintpp==0.4.0 -py==1.10.0 +parse-type==0.6.0 +pluggy==1.0.0 +py==1.11.0 py-cpuinfo==8.0.0 -Pygments==2.9.0 -pyparsing==2.4.7 -pytest==6.2.4 +Pygments==2.12.0 +pyparsing==3.0.9 +pytest==7.1.2 pytest-bdd==4.1.0 pytest-benchmark==3.4.1 -pytest-cov==2.12.1 -pytest-forked==1.3.0 -pytest-icdiff==0.5 +pytest-cov==3.0.0 +pytest-forked==1.4.0 pytest-instafail==0.4.2 -pytest-mock==3.6.1 +pytest-mock==3.7.0 pytest-qt==4.0.2 pytest-repeat==0.9.1 -pytest-rerunfailures==10.1 -pytest-xdist==2.3.0 +pytest-rerunfailures==10.2 +pytest-xdist==2.5.0 pytest-xvfb==2.0.0 -PyVirtualDisplay==2.2 -requests==2.25.1 +PyVirtualDisplay==3.0 +requests==2.28.0 requests-file==1.5.1 six==1.16.0 sortedcontainers==2.4.0 -soupsieve==2.2.1 -tldextract==3.1.0 +soupsieve==2.3.2.post1 +tldextract==3.3.0 toml==0.10.2 -urllib3==1.26.6 -vulture==2.3 -Werkzeug==2.0.1 +tomli==2.0.1 +urllib3==1.26.9 +vulture==2.4 +Werkzeug==2.1.2 +zipp==3.8.0 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index ab580ac4b..f5edc9b6d 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -4,7 +4,8 @@ coverage Flask hypothesis pytest -pytest-bdd +# https://github.com/pytest-dev/pytest-bdd/issues/447 +pytest-bdd<5 pytest-benchmark pytest-instafail pytest-mock @@ -27,8 +28,6 @@ pytest-xvfb PyVirtualDisplay # To run on multiple cores with -n pytest-xdist -# For nicer output -pytest-icdiff # Needed to test misc/userscripts/qute-lastpass tldextract diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 82eb5b25c..533e91e82 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -1,16 +1,16 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -appdirs==1.4.4 -distlib==0.3.2 -filelock==3.0.12 -packaging==21.0 -pip==21.1.3 -pluggy==0.13.1 -py==1.10.0 -pyparsing==2.4.7 -setuptools==57.1.0 +distlib==0.3.4 +filelock==3.7.1 +packaging==21.3 +pip==22.1.2 +platformdirs==2.5.2 +pluggy==1.0.0 +py==1.11.0 +pyparsing==3.0.9 +setuptools==62.3.4 six==1.16.0 toml==0.10.2 -tox==3.23.1 -virtualenv==20.4.7 -wheel==0.36.2 +tox==3.25.0 +virtualenv==20.14.1 +wheel==0.37.1 diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt index 433500bf0..ac5016c99 100644 --- a/misc/requirements/requirements-vulture.txt +++ b/misc/requirements/requirements-vulture.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py toml==0.10.2 -vulture==2.3 +vulture==2.4 diff --git a/misc/requirements/requirements-yamllint.txt b/misc/requirements/requirements-yamllint.txt index a911b9d2b..12553f2b2 100644 --- a/misc/requirements/requirements-yamllint.txt +++ b/misc/requirements/requirements-yamllint.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -pathspec==0.8.1 -PyYAML==5.4.1 -yamllint==1.26.1 +pathspec==0.9.0 +PyYAML==6.0 +yamllint==1.26.3 diff --git a/misc/userscripts/README.md b/misc/userscripts/README.md index f5325127b..5a7a3d51d 100644 --- a/misc/userscripts/README.md +++ b/misc/userscripts/README.md @@ -74,6 +74,8 @@ The following userscripts can be found on their own repositories. and retrieve they when you want. - [doi](https://github.com/cadadr/configuration/blob/master/qutebrowser/userscripts/doi): Opens DOIs on Sci-Hub. +- [qute-1password](https://github.com/fmartingr/qute-1password): + Qutebrowser userscript to fill 1password credentials - [1password](https://github.com/tomoakley/dotfiles/blob/master/qutebrowser/userscripts/1password): Integration with 1password on macOS. - [localhost](https://github.com/SidharthArya/.qutebrowser/blob/master/userscripts/localhost): @@ -83,6 +85,17 @@ The following userscripts can be found on their own repositories. services (Invidious, Teddit, Nitter, Bibliogram, OpenStreetMap). - [CIAvash/qutebrowser-userscripts](https://github.com/CIAvash/qutebrowser-userscripts), various small userscripts written in Raku. +- [bitwarden-rofi](https://github.com/haztecaso/bitwarden-rofi), rofi wrapper for bitwarden cli + interface using gpg instead of keyctl. +- [yomichad](https://github.com/potamides/yomichad): Japanese pop-up dictionary + for looking up readings and definitions of currently selected words, kanji + and names +- [~palb91/qutescripts](https://git.sr.ht/~palb91/qutescripts): A list of + personal userscripts for qutebrowser (`domcycle`: settings per domain, + `gitclone`, `jsdownload`: smart download, and `substiqute`: bash-like url + substitution) +- [pseudo per domain stylesheets](https://github.com/bschnitz/qute) Userscript + for generating url restricted greasemonkey scripts to apply user styles. [Zotero]: https://www.zotero.org/ [Pocket]: https://getpocket.com/ diff --git a/misc/userscripts/cast b/misc/userscripts/cast index df74fe97e..ec703d5fb 100755 --- a/misc/userscripts/cast +++ b/misc/userscripts/cast @@ -20,6 +20,14 @@ # # Dependencies # - castnow, https://github.com/xat/castnow +# - youtube-dl (https://youtube-dl.org/) or a drop-in replacement such as +# yt-dlp (https://github.com/yt-dlp/yt-dlp). +# +# Configuration: +# This script looks at the optional QUTE_CAST_YTDL_PROGRAM environment +# variable (if it exists) to decide which program to use for downloading +# videos. If specified, this should be youtube-dl or a drop-in replacement +# for it. # # Author # Simon Désaulniers <sim.desaulniers@gmail.com> @@ -133,23 +141,34 @@ echo "jseval -q $(printjs)" >> "$QUTE_FIFO" tmpdir=$(mktemp -d) file_to_cast=${tmpdir}/qutecast -program_=$(command -v castnow) +cast_program=$(command -v castnow) + +# pick a ytdl program +for p in "$QUTE_CAST_YTDL_PROGRAM" yt-dlp youtube-dl; do + ytdl_program=$(command -v -- "$p") + [ "$ytdl_program" == "" ] || break +done -if [[ "${program_}" == "" ]]; then - msg error "castnow can't be found..." +if [[ "${cast_program}" == "" ]]; then + msg error "castnow can't be found" + exit 1 +fi +if [[ "${ytdl_program}" == "" ]]; then + msg error "youtube-dl or a drop-in replacement can't be found in PATH, and no installed program " \ + "specified in QUTE_CAST_YTDL_PROGRAM (currently \"$QUTE_CAST_YTDL_PROGRAM\")" exit 1 fi # kill any running instance of castnow -pkill -f "${program_}" +pkill -f -- "${cast_program}" # start youtube download in stream mode (-o -) into temporary file -youtube-dl -qo - "$1" > "${file_to_cast}" & +"${ytdl_program}" -qo - "$1" > "${file_to_cast}" & ytdl_pid=$! msg info "Casting $1" >> "$QUTE_FIFO" # start castnow in stream mode to cast on ChromeCast -tail -F "${file_to_cast}" | ${program_} - +tail -F "${file_to_cast}" | ${cast_program} - # cleanup remaining background process and file on disk kill ${ytdl_pid} diff --git a/misc/userscripts/open_download b/misc/userscripts/open_download index 62730f37c..e118765f7 100755 --- a/misc/userscripts/open_download +++ b/misc/userscripts/open_download @@ -103,13 +103,22 @@ fi file="${entries[$line]}" file="${file%%$'\t'*}" path="$DOWNLOAD_DIR/$file" -filetype=$(xdg-mime query filetype "$path") -application=$(xdg-mime query default "$filetype") -if [ -z "$application" ] ; then - die "Do not know how to open »$file« of type $filetype" -fi +if [ -f /.flatpak-info ]; then + # with the help of the appchooser portal, flatpak let the user select the + # app associated with a mime type after executing xdg-open. + # we can't know ahead of time which app will be launched, and the sandbox + # doesn't have access to mime types known to the host. + msg info "Opening »$file« with XDG Desktop Portal" +else + filetype=$(xdg-mime query filetype "$path") + application=$(xdg-mime query default "$filetype") + + if [ -z "$application" ] ; then + die "Do not know how to open »$file« of type $filetype" + fi -msg info "Opening »$file« (of type $filetype) with ${application%.desktop}" + msg info "Opening »$file« (of type $filetype) with ${application%.desktop}" +fi -xdg-open "$path" & +xdg-open "$path" diff --git a/misc/userscripts/password_fill b/misc/userscripts/password_fill index c46253d41..3ea8fd9f6 100755 --- a/misc/userscripts/password_fill +++ b/misc/userscripts/password_fill @@ -241,7 +241,7 @@ pass_backend() { if $GPG "${GPG_OPTS[@]}" -d "$passfile" \ | grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null then - passfile="${passfile#$PREFIX}" + passfile="${passfile#"$PREFIX"}" passfile="${passfile#/}" files+=( "${passfile%.gpg}" ) fi @@ -250,7 +250,7 @@ pass_backend() { if ((match_filename)) ; then # add entries with matching filepath while read -r passfile ; do - passfile="${passfile#$PREFIX}" + passfile="${passfile#"$PREFIX"}" passfile="${passfile#/}" files+=( "${passfile%.gpg}" ) done < <(find -L "$PREFIX" -iname '*.gpg' | grep "$url") @@ -267,7 +267,7 @@ pass_backend() { else if [[ $line =~ $user_pattern ]] ; then # remove the matching prefix "user: " from the beginning of the line - username=${line#${BASH_REMATCH[0]}} + username=${line#"${BASH_REMATCH[0]}"} break fi fi diff --git a/misc/userscripts/qute-bitwarden b/misc/userscripts/qute-bitwarden index c6407127a..a30f734f2 100755 --- a/misc/userscripts/qute-bitwarden +++ b/misc/userscripts/qute-bitwarden @@ -6,7 +6,7 @@ # This file is part of qutebrowser. # # qutebrowser is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published bjy +# it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # @@ -62,6 +62,8 @@ argument_parser = argparse.ArgumentParser( argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL')) argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu -i -p Bitwarden', help='Invocation used to execute a dmenu-provider') +argument_parser.add_argument('--password-prompt-invocation', '-p', default='rofi -dmenu -p "Master Password" -password -lines 0', + help='Invocation used to prompt the user for their Bitwarden password') argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false', help="Don't automatically enter insert mode") argument_parser.add_argument('--totp', '-t', action='store_true', @@ -98,59 +100,54 @@ def qute_command(command): fifo.flush() -def ask_password(): - process = subprocess.run([ - 'rofi', - '-dmenu', - '-p', - 'Master Password', - '-password', - '-lines', - '0', - ], universal_newlines=True, stdout=subprocess.PIPE) +def ask_password(password_prompt_invocation): + process = subprocess.run( + shlex.split(password_prompt_invocation), + text=True, + stdout=subprocess.PIPE, + ) if process.returncode > 0: raise Exception('Could not unlock vault') master_pass = process.stdout.strip() return subprocess.check_output( ['bw', 'unlock', '--raw', master_pass], - universal_newlines=True, + text=True, ).strip() -def get_session_key(auto_lock): +def get_session_key(auto_lock, password_prompt_invocation): if auto_lock == 0: subprocess.call(['keyctl', 'purge', 'user', 'bw_session']) - return ask_password() + return ask_password(password_prompt_invocation) else: process = subprocess.run( ['keyctl', 'request', 'user', 'bw_session'], - universal_newlines=True, + text=True, stdout=subprocess.PIPE, ) key_id = process.stdout.strip() if process.returncode > 0: - session = ask_password() + session = ask_password(password_prompt_invocation) if not session: raise Exception('Could not unlock vault') key_id = subprocess.check_output( ['keyctl', 'add', 'user', 'bw_session', session, '@u'], - universal_newlines=True, + text=True, ).strip() if auto_lock > 0: subprocess.call(['keyctl', 'timeout', str(key_id), str(auto_lock)]) return subprocess.check_output( ['keyctl', 'pipe', str(key_id)], - universal_newlines=True, + text=True, ).strip() -def pass_(domain, encoding, auto_lock): - session_key = get_session_key(auto_lock) +def pass_(domain, encoding, auto_lock, password_prompt_invocation): + session_key = get_session_key(auto_lock, password_prompt_invocation) process = subprocess.run( ['bw', 'list', 'items', '--session', session_key, '--url', domain], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, ) err = process.stderr.decode(encoding).strip() @@ -166,12 +163,11 @@ def pass_(domain, encoding, auto_lock): return out -def get_totp_code(selection_id, domain_name, encoding, auto_lock): - session_key = get_session_key(auto_lock) +def get_totp_code(selection_id, domain_name, encoding, auto_lock, password_prompt_invocation): + session_key = get_session_key(auto_lock, password_prompt_invocation) process = subprocess.run( ['bw', 'get', 'totp', '--session', session_key, selection_id], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, ) err = process.stderr.decode(encoding).strip() @@ -198,7 +194,7 @@ def dmenu(items, invocation, encoding): def fake_key_raw(text): for character in text: # Escape all characters by default, space requires special handling - sequence = '" "' if character == ' ' else '\{}'.format(character) + sequence = '" "' if character == ' ' else r'\{}'.format(character) qute_command('fake-key {}'.format(sequence)) @@ -216,7 +212,7 @@ def main(arguments): for target in filter(None, [ extract_result.fqdn, extract_result.registered_domain, - extract_result.subdomain + extract_result.domain, + extract_result.subdomain + '.' + extract_result.domain, extract_result.domain, extract_result.ipv4]): target_candidates = json.loads( @@ -224,6 +220,7 @@ def main(arguments): target, arguments.io_encoding, arguments.auto_lock, + arguments.password_prompt_invocation, ) ) if not target_candidates: @@ -270,7 +267,8 @@ def main(arguments): selection['id'], selection['name'], arguments.io_encoding, - arguments.auto_lock + arguments.auto_lock, + arguments.password_prompt_invocation, ) ) else: @@ -294,7 +292,8 @@ def main(arguments): selection['id'], selection['name'], arguments.io_encoding, - arguments.auto_lock + arguments.auto_lock, + arguments.password_prompt_invocation, ) ) diff --git a/misc/userscripts/qute-keepassxc b/misc/userscripts/qute-keepassxc index 11d0a3384..61a6c7bce 100755 --- a/misc/userscripts/qute-keepassxc +++ b/misc/userscripts/qute-keepassxc @@ -43,6 +43,8 @@ config.bind('<Alt-Shift-u>', 'spawn --userscript qute-keepassxc --key ABC1234', config.bind('pw', 'spawn --userscript qute-keepassxc --key ABC1234', mode='normal') ``` +To manage multiple accounts you also need [rofi](https://github.com/davatorium/rofi) installed. + # Usage @@ -62,7 +64,27 @@ Unlike most browser extensions which only use plain local storage, this one atte by storing the key in encrypted form using GPG. Therefore you need to have a public-key-pair readily set up. -GPG might then ask for your private-key passwort whenever you query the database for login credentials. +GPG might then ask for your private-key password whenever you query the database for login credentials. + + +# TOTP + +This script recently received experimental TOTP support. +To use it, you need to have working TOTP authentication within KeepassXC. +Then call `qute-keepassxc` with the `--totp` flags. + +For example, I have the following line in my `config.py`: + +```python +config.bind('pt', 'spawn --userscript qute-keepassxc --key ABC1234 --totp', mode='normal') +``` + +For now this script will simply insert the TOTP-token into the currently selected +input field, since I have not yet found a reliable way to identify the correct field +within all existing login forms. +Thus you need to manually select the TOTP input field, press escape to leave input +mode and then enter `pt` to fill in the token (or configure another key-binding for +insert mode if you prefer that). [1]: https://keepassxc.org/ @@ -88,6 +110,8 @@ import nacl.public def parse_args(): parser = argparse.ArgumentParser(description="Full passwords from KeepassXC") parser.add_argument('url', nargs='?', default=os.environ.get('QUTE_URL')) + parser.add_argument('--totp', action='store_true', + help="Fill in current TOTP field instead of username/password") parser.add_argument('--socket', '-s', default='/run/user/{}/org.keepassxc.KeePassXC.BrowserServer'.format(os.getuid()), help='Path to KeepassXC browser socket') parser.add_argument('--key', '-k', default='alice@example.com', @@ -160,7 +184,7 @@ class KeepassXC: action = 'test-associate', id = self.id, key = base64.b64encode(self.id_key.public_key.encode()).decode('utf-8') - )) + ), triggerUnlock = 'true') return self.recv_msg()['success'] == 'true' def associate(self): @@ -180,6 +204,16 @@ class KeepassXC: )) return self.recv_msg()['entries'] + def get_totp(self, uuid): + self.send_msg(dict( + action = 'get-totp', + uuid = uuid + )) + response = self.recv_msg() + if response['success'] != 'true' or not response['totp']: + return None + return response['totp'] + def send_raw_msg(self, msg): self.sock.send( json.dumps(msg).encode('utf-8') ) @@ -206,16 +240,17 @@ class KeepassXC: class SecretKeyStore: - def __init__(self, gpgkey): + def __init__(self, gpgkey, insecure): self.gpgkey = gpgkey - if gpgkey is None: + self.insecure = insecure + if self.insecure: self.path = os.path.join(os.environ['QUTE_DATA_DIR'], 'keepassxc.key') else: self.path = os.path.join(os.environ['QUTE_DATA_DIR'], 'keepassxc.key.gpg') def load(self): "Load existing association key from file" - if self.gpgkey is None: + if self.insecure: jsondata = open(self.path, 'r').read() else: jsondata = subprocess.check_output(['gpg', '--decrypt', self.path]).decode('utf-8') @@ -232,7 +267,7 @@ class SecretKeyStore: "Store newly created association key in file" self.id = id jsondata = json.dumps({'id':self.id, 'key':base64.b64encode(self.key).decode('utf-8')}) - if self.gpgkey is None: + if self.insecure: open(self.path, "w").write(jsondata) else: subprocess.run(['gpg', '--encrypt', '-o', self.path, '-r', self.gpgkey], input=jsondata.encode('utf-8'), check=True) @@ -250,8 +285,10 @@ def error(msg): def connect_to_keepassxc(args): - assert args.key or args.insecure, "Missing GPG key to use for auth key encryption" - keystore = SecretKeyStore(args.key) + if not args.insecure and not args.key: + error("Missing GPG key to use for auth key encryption") + return + keystore = SecretKeyStore(args.key, args.insecure) if os.path.isfile(keystore.path): keystore.load() kp = KeepassXC(keystore.id, key=keystore.key, socket_path=args.socket) @@ -271,6 +308,30 @@ def connect_to_keepassxc(args): return kp +def select_account(creds): + try: + if len(creds) == 1: + return creds[0] + idx = subprocess.check_output( + ['rofi', '-dmenu', '-format', 'i', '-matching', 'fuzzy', + '-p', 'Search', + '-mesg', '<b>qute-keepassxc</b>: select an account, please!'], + input=b"\n".join(c['login'].encode('utf-8') for c in creds) + ) + idx = int(idx) + if idx < 0: + return None + return creds[idx] + except subprocess.CalledProcessError: + return None + except FileNotFoundError: + error("rofi not found. Please install rofi to select from multiple credentials") + return creds[0] + except Exception as e: + error(f"Error while picking account: {e}") + return None + + def make_js_code(username, password): return ' '.join(""" function isVisible(elem) { @@ -332,6 +393,21 @@ def make_js_code(username, password): """.splitlines()) % (json.dumps(username), json.dumps(password)) +def make_js_totp_code(totp): + return ' '.join(""" + (function () { + var input = document.activeElement; + if (!input || input.tagName !== "INPUT") { + alert("No TOTP input field selected"); + return; + } + input.value = %s; + input.dispatchEvent(new Event('input', { 'bubbles': true })); + input.dispatchEvent(new Event('change', { 'bubbles': true })); + })(); + """.splitlines()) % (json.dumps(totp),) + + def main(): if 'QUTE_FIFO' not in os.environ: print(f"No QUTE_FIFO found - {sys.argv[0]} must be run as a qutebrowser userscript") @@ -348,10 +424,21 @@ def main(): if not creds: error('No credentials found') return - # TODO: handle multiple matches - name, pw = creds[0]['login'], creds[0]['password'] - if name and pw: - qute('jseval -q ' + make_js_code(name, pw)) + cred = select_account(creds) + if not cred: + error('No credentials selected') + return + if args.totp: + uuid = cred['uuid'] + totp = kp.get_totp(uuid) + if not totp: + error('No TOTP key found') + return + qute('jseval -q ' + make_js_totp_code(totp)) + else: + name, pw = cred['login'], cred['password'] + if name and pw: + qute('jseval -q ' + make_js_code(name, pw)) except Exception as e: error(str(e)) diff --git a/misc/userscripts/qute-lastpass b/misc/userscripts/qute-lastpass index d2a72f077..e99a51a0f 100755 --- a/misc/userscripts/qute-lastpass +++ b/misc/userscripts/qute-lastpass @@ -6,7 +6,7 @@ # This file is part of qutebrowser. # # qutebrowser is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published bjy +# it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # @@ -86,7 +86,7 @@ def qute_command(command): def pass_(domain, encoding): domain = re.escape(domain) args = ['lpass', 'show', '-x', '-j', '-G', '\\b{:s}'.format(domain)] - process = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + process = subprocess.run(args, capture_output=True) candidates = json.loads(process.stdout.decode(encoding).strip() or '[]') err = process.stderr.decode(encoding).strip() diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 71646eb69..573644dc4 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -23,11 +23,12 @@ demonstration can be seen here: https://i.imgur.com/KN3XuZP.gif. """ USAGE = """The domain of the site has to appear as a segment in the pass path, -for example: "github.com/cryzed" or "websites/github.com". How the username and -password are determined is freely configurable using the CLI arguments. As an -example, if you instead store the username as part of the secret (and use a -site's name as filename), instead of the default configuration, use -`--username-target secret` and `--username-pattern "username: (.+)"`. +for example: "github.com/cryzed" or "websites/github.com". Alternatively the +parameter `--unfiltered` may be used to get a list of all passwords. How the +username and password are determined is freely configurable using the CLI +arguments. As an example, if you instead store the username as part of the +secret (and use a site's name as filename), instead of the default configuration, +use `--username-target secret` and `--username-pattern "username: (.+)"`. The login information is inserted by emulating key events using qutebrowser's fake-key command in this manner: [USERNAME]<Tab>[PASSWORD], which is compatible @@ -76,6 +77,8 @@ argument_parser.add_argument('--password-store', '-p', help='Path to your pass password-store (only used in pass-mode)', type=expanded_path) argument_parser.add_argument('--mode', '-M', choices=['pass', 'gopass'], default="pass", help='Select mode [gopass] to use gopass instead of the standard pass.') +argument_parser.add_argument('--prefix', type=str, + help='Search only the given subfolder of the store (only used in gopass-mode)') argument_parser.add_argument('--username-pattern', '-u', default=r'.*/(.+)', help='Regular expression that matches the username') argument_parser.add_argument('--username-target', '-U', choices=['path', 'secret'], default='path', @@ -92,6 +95,10 @@ argument_parser.add_argument('--merge-candidates', '-m', action='store_true', help='Merge pass candidates for fully-qualified and registered domain name') argument_parser.add_argument('--extra-url-suffixes', '-s', default='', help='Comma-separated string containing extra suffixes (e.g local)') +argument_parser.add_argument('--unfiltered', dest='unfiltered', action='store_true', + help='Show an unfiltered selection of all passwords in the store') +argument_parser.add_argument('--always-show-selection', dest='always_show_selection', action='store_true', + help='Always show selection, even if there is only a single match') group = argument_parser.add_mutually_exclusive_group() group.add_argument('--username-only', '-e', action='store_true', help='Only insert username') group.add_argument('--password-only', '-w', action='store_true', help='Only insert password') @@ -123,14 +130,17 @@ def qute_command(command): fifo.flush() -def find_pass_candidates(domain): +def find_pass_candidates(domain, unfiltered=False): candidates = [] if arguments.mode == "gopass": - all_passwords = subprocess.run(["gopass", "list", "--flat" ], stdout=subprocess.PIPE).stdout.decode("UTF-8").splitlines() + gopass_args = ["gopass", "list", "--flat"] + if arguments.prefix: + gopass_args.append(arguments.prefix) + all_passwords = subprocess.run(gopass_args, stdout=subprocess.PIPE).stdout.decode("UTF-8").splitlines() for password in all_passwords: - if domain in password: + if unfiltered or domain in password: candidates.append(password) else: for path, directories, file_names in os.walk(arguments.password_store, followlinks=True): @@ -143,7 +153,7 @@ def find_pass_candidates(domain): split_path = pass_path.split(os.path.sep) for secret in secrets: secret_base = os.path.splitext(secret)[0] - if domain not in (split_path + [secret_base]): + if not unfiltered and domain not in (split_path + [secret_base]): continue candidates.append(os.path.join(pass_path, secret_base)) @@ -178,7 +188,7 @@ def dmenu(items, invocation): def fake_key_raw(text): for character in text: # Escape all characters by default, space requires special handling - sequence = '" "' if character == ' ' else '\{}'.format(character) + sequence = '" "' if character == ' ' else r'\{}'.format(character) qute_command('fake-key {}'.format(sequence)) @@ -223,7 +233,7 @@ def main(arguments): for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.ipv4, private_domain]): attempted_targets.append(target) - target_candidates = find_pass_candidates(target) + target_candidates = find_pass_candidates(target, unfiltered=arguments.unfiltered) if not target_candidates: continue @@ -235,7 +245,11 @@ def main(arguments): stderr('No pass candidates for URL {!r} found! (I tried {!r})'.format(arguments.url, attempted_targets)) return ExitCodes.NO_PASS_CANDIDATES - selection = candidates.pop() if len(candidates) == 1 else dmenu(sorted(candidates), arguments.dmenu_invocation) + if len(candidates) == 1 and not arguments.always_show_selection: + selection = candidates.pop() + else: + selection = dmenu(sorted(candidates), arguments.dmenu_invocation) + # Nothing was selected, simply return if not selection: return ExitCodes.SUCCESS diff --git a/misc/userscripts/qutedmenu b/misc/userscripts/qutedmenu index bdd0d9b27..7f326916b 100755 --- a/misc/userscripts/qutedmenu +++ b/misc/userscripts/qutedmenu @@ -48,6 +48,8 @@ url=${url/*http/http} [[ -z $url ]] && exit 0 case $1 in - open) printf '%s' "open $url" >> "$QUTE_FIFO" || qutebrowser "$url" ;; - tab) printf '%s' "open -t $url" >> "$QUTE_FIFO" || qutebrowser "$url" ;; + open) printf '%s' "open $url" >> "$QUTE_FIFO" || qutebrowser "$url" ;; + tab) printf '%s' "open -t $url" >> "$QUTE_FIFO" || qutebrowser "$url" ;; + window) printf '%s' "open -w $url" >> "$QUTE_FIFO" || qutebrowser "$url --target window" ;; + private) printf '%s' "open -p $url" >> "$QUTE_FIFO" || qutebrowser "$url --target private-window" ;; esac diff --git a/misc/userscripts/readability b/misc/userscripts/readability index a6a6f2d52..19b687939 100755 --- a/misc/userscripts/readability +++ b/misc/userscripts/readability @@ -10,7 +10,6 @@ # Usage: # :spawn --userscript readability # -from __future__ import absolute_import import codecs, os tmpfile = os.path.join( diff --git a/misc/userscripts/readability-js b/misc/userscripts/readability-js index 485957ddb..752b759bb 100755 --- a/misc/userscripts/readability-js +++ b/misc/userscripts/readability-js @@ -59,9 +59,6 @@ const HEADER = ` width: 100%; margin: 0 0; } - a.reader-title { - color: #FFFFFF !important; - } img { max-width:100%; height:auto; diff --git a/misc/userscripts/ripbang b/misc/userscripts/ripbang index df9cb2fbc..2f867c838 100755 --- a/misc/userscripts/ripbang +++ b/misc/userscripts/ripbang @@ -9,26 +9,22 @@ # :spawn --userscript ripbang amazon maps # -from __future__ import print_function import os, re, requests, sys - -try: - from urllib.parse import unquote -except ImportError: - from urllib import unquote +from urllib.parse import urlparse, parse_qs for argument in sys.argv[1:]: bang = '!' + argument r = requests.get('https://duckduckgo.com/', - params={'q': bang + ' SEARCHTEXT'}) + params={'q': bang + ' SEARCHTEXT'}, + headers={'user-agent': 'qutebrowser ripbang'}) - searchengine = unquote(re.search("url=[^']+", r.text).group(0)) - searchengine = searchengine.replace('url=', '') - searchengine = searchengine.replace('/l/?kh=-1&uddg=', '') + searchengine = re.search("url=([^']+)", r.text).group(1) + searchengine = urlparse(searchengine).query + searchengine = parse_qs(searchengine)['uddg'][0] searchengine = searchengine.replace('SEARCHTEXT', '{}') if os.getenv('QUTE_FIFO'): with open(os.environ['QUTE_FIFO'], 'w') as fifo: - fifo.write('set searchengines %s %s' % (bang, searchengine)) + fifo.write('config-dict-add url.searchengines %s %s' % (bang, searchengine)) else: print('%s %s' % (bang, searchengine)) diff --git a/pytest.ini b/pytest.ini index 72ad24002..d32746281 100644 --- a/pytest.ini +++ b/pytest.ini @@ -86,4 +86,9 @@ filterwarnings = ignore:_SixMetaPathImporter\.find_spec\(\) not found; falling back to find_module\(\):ImportWarning # https://github.com/ionelmc/python-hunter/issues/97 ignore:The distutils\.sysconfig module is deprecated, use sysconfig instead:DeprecationWarning + # https://github.com/certifi/python-certifi/issues/170 + ignore:path is deprecated\. Use files\(\) instead\. Refer to https.//importlib-resources\.readthedocs\.io/en/latest/using\.html#migrating-from-legacy for migration advice\.:DeprecationWarning:certifi.core + # https://github.com/HypothesisWorks/hypothesis/issues/3309 + ignore:module 'sre_constants' is deprecated:DeprecationWarning + ignore:module 'sre_parse' is deprecated:DeprecationWarning faulthandler_timeout = 90 diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index d146eb8bf..75d4e0532 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. -"""A keyboard-driven, vim-like browser based on PyQt5.""" +"""A keyboard-driven, vim-like browser based on Python and Qt.""" import os.path @@ -26,8 +26,8 @@ __copyright__ = "Copyright 2014-2021 Florian Bruhin (The Compiler)" __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version__ = "2.3.0" +__version__ = "2.5.1" __version_info__ = tuple(int(part) for part in __version__.split('.')) -__description__ = "A keyboard-driven, vim-like browser based on PyQt5." +__description__ = "A keyboard-driven, vim-like browser based on Python and Qt." basedir = os.path.dirname(os.path.realpath(__file__)) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 1ab28e8d0..f74617133 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -51,7 +51,6 @@ from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon from PyQt5.QtCore import pyqtSlot, QUrl, QObject, QEvent, pyqtSignal, Qt import qutebrowser -import qutebrowser.resources from qutebrowser.commands import runners from qutebrowser.config import (config, websettings, configfiles, configinit, qtargs) @@ -182,8 +181,9 @@ def _init_icon(): """Initialize the icon of qutebrowser.""" fallback_icon = QIcon() for size in [16, 24, 32, 48, 64, 96, 128, 256, 512]: - filename = ':/icons/qutebrowser-{size}x{size}.png'.format(size=size) - pixmap = QPixmap(filename) + filename = 'icons/qutebrowser-{size}x{size}.png'.format(size=size) + pixmap = QPixmap() + pixmap.loadFromData(resources.read_file_binary(filename)) if pixmap.isNull(): log.init.warning("Failed to load {}".format(filename)) else: @@ -562,8 +562,7 @@ class Application(QApplication): log.init.debug("Initializing application...") self.launch_time = datetime.datetime.now() - self.focusObjectChanged.connect( # type: ignore[attr-defined] - self.on_focus_object_changed) + self.focusObjectChanged.connect(self.on_focus_object_changed) self.setAttribute(Qt.AA_UseHighDpiPixmaps, True) self.new_window.connect(self._on_new_window) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index b1827dbf4..699fe1b0b 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -24,7 +24,7 @@ import itertools import functools import dataclasses from typing import (cast, TYPE_CHECKING, Any, Callable, Iterable, List, Optional, - Sequence, Set, Type, Union) + Sequence, Set, Type, Union, Tuple) from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt, QEvent, QPoint, QRect) @@ -35,12 +35,12 @@ from PyQt5.QtNetwork import QNetworkAccessManager if TYPE_CHECKING: from PyQt5.QtWebKit import QWebHistory, QWebHistoryItem - from PyQt5.QtWebKitWidgets import QWebPage + from PyQt5.QtWebKitWidgets import QWebPage, QWebView from PyQt5.QtWebEngineWidgets import ( - QWebEngineHistory, QWebEngineHistoryItem, QWebEnginePage) + QWebEngineHistory, QWebEngineHistoryItem, QWebEnginePage, QWebEngineView) from qutebrowser.keyinput import modeman -from qutebrowser.config import config +from qutebrowser.config import config, websettings from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils, urlutils, message, jinja) from qutebrowser.misc import miscwidgets, objects, sessions @@ -53,6 +53,7 @@ if TYPE_CHECKING: tab_id_gen = itertools.count(0) +_WidgetType = Union["QWebView", "QWebEngineView"] def create(win_id: int, @@ -156,7 +157,7 @@ class AbstractAction: action_base: Type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']] def __init__(self, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def exit_fullscreen(self) -> None: @@ -172,6 +173,7 @@ class AbstractAction: member = getattr(self.action_class, name, None) if not isinstance(member, self.action_base): raise WebTabError("{} is not a valid web action!".format(name)) + assert member is not None # for mypy self._widget.triggerPageAction(member) def show_source(self, pygments: bool = False) -> None: @@ -229,7 +231,7 @@ class AbstractPrinting: """Attribute ``printing`` of AbstractTab for printing the page.""" def __init__(self, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def check_pdf_support(self) -> None: @@ -308,7 +310,7 @@ class AbstractSearch(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self.text: Optional[str] = None self.search_displayed = False @@ -372,7 +374,7 @@ class AbstractZoom(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None: super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) # Whether zoom was changed from the default. self._default_zoom_changed = False self._init_neighborlist() @@ -466,7 +468,7 @@ class AbstractCaret(QObject): mode_manager: modeman.ModeManager, parent: QWidget = None) -> None: super().__init__(parent) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._mode_manager = mode_manager mode_manager.entered.connect(self._on_mode_entered) mode_manager.left.connect(self._on_mode_left) @@ -559,7 +561,7 @@ class AbstractScroller(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) if 'log-scroll-pos' in objects.debug_flags: self.perc_changed.connect(self._log_scroll_pos_change) @@ -568,16 +570,16 @@ class AbstractScroller(QObject): log.webview.vdebug( # type: ignore[attr-defined] "Scroll position changed to {}".format(self.pos_px())) - def _init_widget(self, widget: QWidget) -> None: + def _init_widget(self, widget: _WidgetType) -> None: self._widget = widget - def pos_px(self) -> int: + def pos_px(self) -> QPoint: raise NotImplementedError - def pos_perc(self) -> int: + def pos_perc(self) -> Tuple[int, int]: raise NotImplementedError - def to_perc(self, x: int = None, y: int = None) -> None: + def to_perc(self, x: float = None, y: float = None) -> None: raise NotImplementedError def to_point(self, point: QPoint) -> None: @@ -627,6 +629,8 @@ class AbstractHistoryPrivate: """Private API related to the history.""" + _history: Union["QWebHistory", "QWebEngineHistory"] + def serialize(self) -> bytes: """Serialize into an opaque format understood by self.deserialize.""" raise NotImplementedError @@ -711,7 +715,7 @@ class AbstractElements: _ErrorCallback = Callable[[Exception], None] def __init__(self, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def find_css(self, selector: str, @@ -772,13 +776,14 @@ class AbstractAudio(QObject): def __init__(self, tab: 'AbstractTab', parent: QWidget = None) -> None: super().__init__(parent) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab def set_muted(self, muted: bool, override: bool = False) -> None: """Set this tab as muted or not. Arguments: + muted: Whether the tab is currently muted. override: If set to True, muting/unmuting was done manually and overrides future automatic mute/unmute changes based on the URL. @@ -803,11 +808,11 @@ class AbstractTabPrivate: def __init__(self, mode_manager: modeman.ModeManager, tab: 'AbstractTab') -> None: - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._tab = tab self._mode_manager = mode_manager - def event_target(self) -> QWidget: + def event_target(self) -> Optional[QWidget]: """Return the widget events should be sent to.""" raise NotImplementedError @@ -847,7 +852,7 @@ class AbstractTabPrivate: def shutdown(self) -> None: raise NotImplementedError - def run_js_sync(self, code: str) -> None: + def run_js_sync(self, code: str) -> Any: """Run javascript sync. Result will be returned when running JS is complete. @@ -866,7 +871,7 @@ class AbstractTabPrivate: self._tab.data.inspector = None self.toggle_inspector(inspector.Position.window) - def toggle_inspector(self, position: inspector.Position) -> None: + def toggle_inspector(self, position: Optional[inspector.Position]) -> None: """Show/hide (and if needed, create) the web inspector for this tab.""" tabdata = self._tab.data if tabdata.inspector is None: @@ -943,6 +948,19 @@ class AbstractTab(QWidget): # for a given hostname anyways. _insecure_hosts: Set[str] = set() + # Sub-APIs initialized by subclasses + history: AbstractHistory + scroller: AbstractScroller + caret: AbstractCaret + zoom: AbstractZoom + search: AbstractSearch + printing: AbstractPrinting + action: AbstractAction + elements: AbstractElements + audio: AbstractAudio + private_api: AbstractTabPrivate + settings: websettings.AbstractSettings + def __init__(self, *, win_id: int, mode_manager: 'modeman.ModeManager', private: bool, @@ -961,7 +979,7 @@ class AbstractTab(QWidget): self.data = TabData() self._layout = miscwidgets.WrapperLayout(self) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._progress = 0 self._load_status = usertypes.LoadStatus.none self._tab_event_filter = eventfilter.TabEventFilter( @@ -975,7 +993,7 @@ class AbstractTab(QWidget): self.before_load_started.connect(self._on_before_load_started) - def _set_widget(self, widget: QWidget) -> None: + def _set_widget(self, widget: Union["QWebView", "QWebEngineView"]) -> None: # pylint: disable=protected-access self._widget = widget self.data.splitter = miscwidgets.InspectorSplitter( @@ -1194,7 +1212,7 @@ class AbstractTab(QWidget): def title(self) -> str: raise NotImplementedError - def icon(self) -> None: + def icon(self) -> QIcon: raise NotImplementedError def set_html(self, html: str, base_url: QUrl = QUrl()) -> None: diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 8cd73ae4f..4f782c3ee 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -383,17 +383,18 @@ class CommandDispatcher: yield parsed @cmdutils.register(instance='command-dispatcher', scope='window') - def tab_clone(self, bg=False, window=False): + def tab_clone(self, bg=False, window=False, private=False): """Duplicate the current tab. Args: bg: Open in a background tab. window: Open in a new window. + private: Open in a new private window. Return: The new QWebView. """ - cmdutils.check_exclusive((bg, window), 'bw') + cmdutils.check_exclusive((bg, window, private), 'bwp') curtab = self._current_widget() cur_title = self._tabbed_browser.widget.page_title( self._current_index()) @@ -404,9 +405,9 @@ class CommandDispatcher: # The new tab could be in a new tabbed_browser (e.g. because of # tabs.tabs_are_windows being set) - if window: + if window or private: new_tabbed_browser = self._new_tabbed_browser( - private=self._tabbed_browser.is_private) + private=self._tabbed_browser.is_private or private) else: new_tabbed_browser = self._tabbed_browser newtab = new_tabbed_browser.tabopen(background=bg) @@ -450,7 +451,7 @@ class CommandDispatcher: self._open(tab.url(), tab=True) if not keep: - tabbed_browser.close_tab(tab, add_undo=False) + tabbed_browser.close_tab(tab, add_undo=False, transfer=True) @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('win_id', completion=miscmodels.window) @@ -499,7 +500,8 @@ class CommandDispatcher: tabbed_browser.tabopen(self._current_url()) if not keep: self._tabbed_browser.close_tab(self._current_widget(), - add_undo=False) + add_undo=False, + transfer=True) def _back_forward(self, tab, bg, window, count, forward, index=None): """Helper function for :back/:forward.""" @@ -911,6 +913,7 @@ class CommandDispatcher: # Not sure how you enter a command without an active window... raise cmdutils.CommandError( "No window specified and couldn't find active window!") + assert isinstance(active_win, mainwindow.MainWindow), active_win win_id = active_win.win_id if win_id not in objreg.window_registry: @@ -1003,11 +1006,10 @@ class CommandDispatcher: raise cmdutils.CommandError("There's no tab with index {}!".format( index)) - @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('index', choices=['+', '-']) - @cmdutils.argument('count', value=cmdutils.Value.count) - def tab_move(self, index: Union[str, int] = None, - count: int = None) -> None: + @cmdutils.register(instance="command-dispatcher", scope="window") + @cmdutils.argument("index", choices=["+", "-", "start", "end"]) + @cmdutils.argument("count", value=cmdutils.Value.count) + def tab_move(self, index: Union[str, int] = None, count: int = None) -> None: """Move the current tab according to the argument and [count]. If neither is given, move it to the first position. @@ -1016,24 +1018,30 @@ class CommandDispatcher: index: `+` or `-` to move relative to the current tab by count, or a default of 1 space. A tab index to move to that index. + `start` and `end` to move to the start and the end. count: If moving relatively: Offset. If moving absolutely: New position (default: 0). This overrides the index argument, if given. """ - if index in ['+', '-']: + if index in ["+", "-"]: # relative moving new_idx = self._current_index() delta = 1 if count is None else count - if index == '-': + if index == "-": new_idx -= delta - elif index == '+': # pragma: no branch + elif index == "+": # pragma: no branch new_idx += delta if config.val.tabs.wrap: new_idx %= self._count() else: + # pylint: disable=else-if-used # absolute moving - if count is not None: + if index == "start": + new_idx = 0 + elif index == "end": + new_idx = self._count() - 1 + elif count is not None: new_idx = count - 1 elif index is not None: assert isinstance(index, int) @@ -1145,6 +1153,7 @@ class CommandDispatcher: idx = self._current_index() if idx != -1: + env['QUTE_TAB_INDEX'] = str(idx + 1) env['QUTE_TITLE'] = self._tabbed_browser.widget.page_title(idx) # FIXME:qtwebengine: If tab is None, run_async will fail! @@ -1514,6 +1523,7 @@ class CommandDispatcher: Callback for GUIProcess when the edited text was updated. Args: + ed: The editor.ExternalEditor instance elem: The WebElementWrapper which was modified. text: The new text to insert. """ diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index fa4ac9da2..32bfd2693 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -611,7 +611,11 @@ class AbstractDownloadItem(QObject): message.error(str(e)) def url(self) -> QUrl: - """Get the download's origin URL.""" + """Get the download's URL (i.e. where the file is downloaded from).""" + raise NotImplementedError + + def origin(self) -> QUrl: + """Get the download's origin URL (i.e. the page starting the download).""" raise NotImplementedError def _get_open_filename(self): @@ -819,6 +823,34 @@ class AbstractDownloadItem(QObject): self.pdfjs_requested.emit(os.path.basename(filename), self.url()) + def cancel_for_origin(self) -> bool: + """Cancel the download based on URL/origin. + + For some special cases, we want to cancel downloads immediately, before + downloading: + + - file:// downloads from file:// URLs (open the file instead) + - http:// downloads from https:// URLs (mixed content) + """ + origin = self.origin() + url = self.url() + if not origin.isValid(): + return False + + if url.scheme() == "file" and origin.scheme() == "file": + utils.open_file(url.toLocalFile()) + self.cancel() + return True + + if (url.scheme() == "http" and + origin.isValid() and origin.scheme() == "https" and + config.instance.get("downloads.prevent_mixed_content", url=origin)): + self._die("Aborting insecure download from secure page " + "(see downloads.prevent_mixed_content).") + return True + + return False + def set_target(self, target): """Set the target for a given download. @@ -1320,6 +1352,7 @@ class TempDownloadManager: The tempfile.TemporaryDirectory that is used. """ if self._tmpdir is None: + # pylint: disable=consider-using-with self._tmpdir = tempfile.TemporaryDirectory( prefix='qutebrowser-downloads-') return self._tmpdir @@ -1341,6 +1374,7 @@ class TempDownloadManager: suggested_name = utils.sanitize_filename(suggested_name) # Make sure that the filename is not too long suggested_name = utils.elide_filename(suggested_name, 50) + # pylint: disable=consider-using-with fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False, suffix='_' + suggested_name) self.files.append(fobj) diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 69c58741a..8187a1002 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -86,6 +86,15 @@ class DownloadView(QListView): count = model.rowCount() return utils.get_repr(self, count=count) + def _model(self) -> downloads.DownloadModel: + """Get the current download model. + + Ensures the model is not None. + """ + model = self.model() + assert isinstance(model, downloads.DownloadModel), model + return model + @pyqtSlot() def _update_geometry(self): """Wrapper to call updateGeometry. @@ -112,7 +121,7 @@ class DownloadView(QListView): """ if not index.isValid(): return - item = self.model().data(index, downloads.ModelRole.item) + item = self._model().data(index, downloads.ModelRole.item) if item.done and item.successful: item.open_file() item.remove() @@ -126,7 +135,7 @@ class DownloadView(QListView): Args: item: The DownloadItem to get the actions for, or None. """ - model = self.model() + model = self._model() actions: _ActionListType = [] if item is None: pass @@ -154,7 +163,7 @@ class DownloadView(QListView): """Show the context menu.""" index = self.indexAt(point) if index.isValid(): - item = self.model().data(index, downloads.ModelRole.item) + item = self._model().data(index, downloads.ModelRole.item) else: item = None self._menu = QMenu(self) @@ -176,7 +185,7 @@ class DownloadView(QListView): def sizeHint(self): """Return sizeHint based on the view contents.""" - idx = self.model().last_index() + idx = self._model().last_index() bottom = self.visualRect(idx).bottom() if bottom != -1: margins = self.contentsMargins() diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index d0245937f..3b9c321e3 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -324,6 +324,7 @@ class GreasemonkeyManager(QObject): """Add a GreasemonkeyScript to this manager. Args: + script: The GreasemonkeyScript to add. force: Fetch and overwrite any dependencies which are already locally cached. """ @@ -379,7 +380,7 @@ class GreasemonkeyManager(QObject): """ # See if we are still waiting on any required scripts for this one for dl in self._in_progress_dls: - if dl.requested_url in script.requires: + if dl.requested_url in script.requires: # type: ignore[attr-defined] return False # Need to add the required scripts to the IIFE now @@ -413,6 +414,7 @@ class GreasemonkeyManager(QObject): force_overwrite=True) download = download_manager.get(QUrl(url), target=target, auto_remove=True) + # FIXME:mypy Build this into downloads instead of patching here? download.requested_url = url self._in_progress_dls.append(download) if download.successful: diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index e127cd10a..ed933e10c 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -254,8 +254,8 @@ class HintActions: flags = QUrl.FullyEncoded | QUrl.RemovePassword if url.scheme() == 'mailto': - flags |= QUrl.RemoveScheme - urlstr = url.toString(flags) # type: ignore[arg-type] + flags |= QUrl.RemoveScheme # type: ignore[operator] + urlstr = url.toString(flags) new_content = urlstr @@ -299,7 +299,7 @@ class HintActions: Args: elem: The QWebElement to download. - _context: The HintContext to use. + context: The HintContext to use. """ url = elem.resolve_url(context.baseurl) if url is None: @@ -356,8 +356,7 @@ class HintActions: url: The URL to open as a QUrl. context: The HintContext to use. """ - urlstr = url.toString( - QUrl.FullyEncoded | QUrl.RemovePassword) # type: ignore[arg-type] + urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword) args = context.get_args(urlstr) commandrunner = runners.CommandRunner(self._win_id) commandrunner.run_safely('spawn ' + ' '.join(args)) @@ -596,6 +595,7 @@ class HintManager(QObject): "'args' is required with target userscript/spawn/run/" "fill.") else: + # pylint: disable=else-if-used if args: raise cmdutils.CommandError( "'args' is only allowed with target userscript/spawn.") @@ -656,6 +656,7 @@ class HintManager(QObject): self._context.labels[string] = label keyparser = self._get_keyparser(usertypes.KeyMode.hint) + assert isinstance(keyparser, modeparsers.HintKeyParser), keyparser keyparser.update_bindings(strings) modeman.enter(self._win_id, usertypes.KeyMode.hint, @@ -684,9 +685,8 @@ class HintManager(QObject): Args: rapid: Whether to do rapid hinting. With rapid hinting, the hint mode isn't left after a hint is followed, so you can easily - open multiple links. This is only possible with targets - `tab` (with `tabs.background=true`), `tab-bg`, - `window`, `run`, `hover`, `userscript` and `spawn`. + open multiple links. Note this won't work with targets + `tab-fg`, `fill`, `delete` and `right-click`. add_history: Whether to add the spawned or yanked link to the browsing history. first: Click the first hinted element without prompting. @@ -754,18 +754,16 @@ class HintManager(QObject): if mode_manager.mode == usertypes.KeyMode.hint: modeman.leave(self._win_id, usertypes.KeyMode.hint, 're-hinting') - if rapid: - if target in [Target.tab_bg, Target.window, Target.run, - Target.hover, Target.userscript, Target.spawn, - Target.download, Target.normal, Target.current, - Target.yank, Target.yank_primary]: - pass - elif target == Target.tab and config.val.tabs.background: - pass - else: - name = target.name.replace('_', '-') - raise cmdutils.CommandError("Rapid hinting makes no sense " - "with target {}!".format(name)) + no_rapid_targets = [ + Target.tab_fg, # opens new tab + Target.fill, # exits hint mode + Target.right_click, # opens multiple context menus + Target.delete, # deleting elements shifts them + ] + if rapid and target in no_rapid_targets: + name = target.name.replace('_', '-') + raise cmdutils.CommandError( + f"Rapid hinting makes no sense with target {name}!") self._check_args(target, *args) @@ -854,6 +852,7 @@ class HintManager(QObject): # apply auto_follow_timeout timeout = config.val.hints.auto_follow_timeout normal_parser = self._get_keyparser(usertypes.KeyMode.normal) + assert isinstance(normal_parser, modeparsers.NormalKeyParser), normal_parser normal_parser.set_inhibited_timeout(timeout) # unpacking gets us the first (and only) key in the dict. self._fire(*visible) @@ -873,12 +872,11 @@ class HintManager(QObject): label.update_text(matched, rest) # Show label again if it was hidden before label.show() - else: + elif (not self._context.rapid or + config.val.hints.hide_unmatched_rapid_hints): # element doesn't match anymore -> hide it, unless in rapid # mode and hide_unmatched_rapid_hints is false (see #1799) - if (not self._context.rapid or - config.val.hints.hide_unmatched_rapid_hints): - label.hide() + label.hide() except webelem.Error: pass self._handle_auto_follow(keystr=keystr) @@ -930,6 +928,7 @@ class HintManager(QObject): self._context.labels[string] = label keyparser = self._get_keyparser(usertypes.KeyMode.hint) + assert isinstance(keyparser, modeparsers.HintKeyParser), keyparser keyparser.update_bindings(strings, preserve_filter=True) # Note: filter_hints can be called with non-None filterstr only @@ -1157,7 +1156,6 @@ class WordHinter: from the words arg as fallback. Args: - words: Words to use as fallback when no link text can be used. elems: The elements to get hint strings for. Return: diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 559992327..d2046345f 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -405,6 +405,7 @@ class WebHistory(sql.SqlTable): Args: url: A url (as QUrl) to add to the history. + title: The tab title to add. redirect: Whether the entry was redirected to another URL (hidden in completion) atime: Override the atime used to add the entry diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index 2b40e97e4..0eafa0536 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -22,19 +22,23 @@ import base64 import binascii import enum -from typing import cast, Optional +from typing import cast, Optional, Any from PyQt5.QtWidgets import QWidget from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent from PyQt5.QtGui import QCloseEvent from qutebrowser.browser import eventfilter -from qutebrowser.config import configfiles +from qutebrowser.config import configfiles, config from qutebrowser.utils import log, usertypes from qutebrowser.keyinput import modeman from qutebrowser.misc import miscwidgets +# FIXME:mypy How to annotate this properly without running into Liskov issues? +_WidgetType = Any + + class Position(enum.Enum): """Where the inspector is shown.""" @@ -93,7 +97,7 @@ class AbstractWebInspector(QWidget): win_id: int, parent: QWidget = None) -> None: super().__init__(parent) - self._widget = cast(QWidget, None) + self._widget = cast(_WidgetType, None) self._layout = miscwidgets.WrapperLayout(self) self._splitter = splitter self._position: Optional[Position] = None @@ -105,7 +109,7 @@ class AbstractWebInspector(QWidget): eventfilter=self._event_filter, parent=self) - def _set_widget(self, widget: QWidget) -> None: + def _set_widget(self, widget: _WidgetType) -> None: self._widget = widget self._widget.setWindowTitle("Web Inspector") self._widget.installEventFilter(self._child_event_filter) @@ -133,7 +137,8 @@ class AbstractWebInspector(QWidget): @pyqtSlot() def _on_clicked(self) -> None: """Enter insert mode if a docked inspector was clicked.""" - if self._position != Position.window: + if (self._position != Position.window and + config.val.input.insert_mode.auto_enter): modeman.enter(self._win_id, usertypes.KeyMode.insert, reason='Inspector clicked', only_if_normal=True) @@ -198,7 +203,8 @@ class AbstractWebInspector(QWidget): geom = base64.b64encode(data).decode('ASCII') configfiles.state['inspector']['window'] = geom - def inspect(self, page: QWidget) -> None: + # FIXME:mypy How to annotate 'page' properly without running into Liskov issues? + def inspect(self, page: Any) -> None: """Inspect the given QWeb(Engine)Page.""" raise NotImplementedError diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index 82bf57136..6217c8d00 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -92,9 +92,6 @@ def incdec(url, count, inc_or_dec): url: The current url. count: How much to increment or decrement by. inc_or_dec: Either 'increment' or 'decrement'. - tab: Whether to open the link in a new tab. - background: Open the link in a new background tab. - window: Open the link in a new window. """ urlutils.ensure_valid(url) segments: Optional[Set[str]] = ( diff --git a/qutebrowser/browser/network/pac.py b/qutebrowser/browser/network/pac.py index 3a544c78f..99078ab20 100644 --- a/qutebrowser/browser/network/pac.py +++ b/qutebrowser/browser/network/pac.py @@ -270,8 +270,7 @@ class PACFetcher(QObject): """Fetch the proxy from the remote URL.""" assert self._manager is not None self._reply = self._manager.get(QNetworkRequest(self._pac_url)) - self._reply.finished.connect( # type: ignore[attr-defined] - self._finish) + self._reply.finished.connect(self._finish) @pyqtSlot() def _finish(self): diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index e178fb0e5..a720402f5 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -110,6 +110,7 @@ class DownloadItem(downloads.AbstractDownloadItem): """Create a file object using the internal filename.""" assert self._filename is not None try: + # pylint: disable=consider-using-with fileobj = open(self._filename, 'wb') except OSError as e: self._die(e.strerror) @@ -202,6 +203,17 @@ class DownloadItem(downloads.AbstractDownloadItem): # Note: self._reply is deleted when the download finishes return self._url + def origin(self) -> QUrl: + if self._reply is None: + return QUrl() + origin = self._reply.request().originatingObject() + try: + return origin.url() + except AttributeError: + # Raised either if origin is None or some object that doesn't + # have its own url. + return QUrl() + def _ensure_can_set_filename(self, filename): if self.fileobj is not None: # pragma: no cover raise ValueError("fileobj was already set! filename: {}, " @@ -491,6 +503,7 @@ class DownloadManager(downloads.AbstractDownloadManager): Args: request: The QNetworkRequest to download. target: Where to save the download as downloads.DownloadTarget. + suggested_fn: The filename to use for the file. **kwargs: Passed to _fetch_request. Return: @@ -535,6 +548,9 @@ class DownloadManager(downloads.AbstractDownloadManager): target: Where to save the download as downloads.DownloadTarget. auto_remove: Whether to remove the download even if downloads.remove_finished is set to -1. + suggested_filename: The filename to use for the file. + prompt_download_directory: Whether to prompt for a location to + download the file to. Return: The created DownloadItem. @@ -549,6 +565,9 @@ class DownloadManager(downloads.AbstractDownloadManager): download = DownloadItem(reply, manager=self) self._init_item(download, auto_remove, suggested_filename) + if download.cancel_for_origin(): + return download + if target is not None: download.set_target(target) return download @@ -582,6 +601,7 @@ class DownloadManager(downloads.AbstractDownloadManager): """ assert nam.adopted_downloads == 0 for download in self.downloads: + assert isinstance(download, DownloadItem), download if download._uses_nam(nam): # pylint: disable=protected-access nam.adopt_download(download) return nam.adopted_downloads diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 68e36d249..c0da8ac94 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -128,9 +128,7 @@ def data_for_url(url: QUrl) -> Tuple[str, bytes]: Return: A (mimetype, data) tuple. """ - norm_url = url.adjusted( - QUrl.NormalizePathSegments | # type: ignore[arg-type] - QUrl.StripTrailingSlash) + norm_url = url.adjusted(QUrl.NormalizePathSegments | QUrl.StripTrailingSlash) if norm_url != url: raise Redirect(norm_url) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 8d3ebe730..3ea323d96 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -51,7 +51,9 @@ def custom_headers(url): conf_headers = config.instance.get('content.headers.custom', url=url) for header, value in conf_headers.items(): - headers[header.encode('ascii')] = value.encode('ascii') + encoded_header = header.encode('ascii') + encoded_value = b"" if value is None else value.encode('ascii') + headers[encoded_header] = encoded_value accept_language = config.instance.get('content.headers.accept_language', url=url) @@ -339,7 +341,8 @@ def get_user_stylesheet(searching=False): 'misc-mathml-darkmode' not in config.val.content.site_specific_quirks.skip): # WORKAROUND for MathML-output on Wikipedia being black on black. # See https://bugs.chromium.org/p/chromium/issues/detail?id=1126606 - css += '\nimg.mwe-math-fallback-image-inline { filter: invert(100%); }' + css += ('\nimg.mwe-math-fallback-image-inline, ' + 'img.mwe-math-fallback-image-display { filter: invert(100%); }') return css @@ -418,12 +421,11 @@ def choose_file(qb_mode: FileSelectionMode) -> List[str]: }[qb_mode] use_tmp_file = any('{}' in arg for arg in command[1:]) if use_tmp_file: - handle = tempfile.NamedTemporaryFile( + with tempfile.NamedTemporaryFile( prefix='qutebrowser-fileselect-', delete=False, - ) - handle.close() - tmpfilename = handle.name + ) as handle: + tmpfilename = handle.name with utils.cleanup_file(tmpfilename): command = ( command[:1] + @@ -509,6 +511,7 @@ def _validated_selected_files( ) continue else: + # pylint: disable=else-if-used if not os.path.isfile(selected_file): message.warning( f"Expected file but got folder, ignoring '{selected_file}'" diff --git a/qutebrowser/browser/signalfilter.py b/qutebrowser/browser/signalfilter.py index 0b002e345..88ac4a65d 100644 --- a/qutebrowser/browser/signalfilter.py +++ b/qutebrowser/browser/signalfilter.py @@ -88,6 +88,7 @@ class SignalFilter(QObject): debug.dbg_signal(signal, args), tabidx)) signal.emit(*args) else: + # pylint: disable=else-if-used if log_signal: log.signals.debug("ignoring: {} (tab {})".format( debug.dbg_signal(signal, args), tabidx)) diff --git a/qutebrowser/browser/urlmarks.py b/qutebrowser/browser/urlmarks.py index 944ec23d4..4e173f293 100644 --- a/qutebrowser/browser/urlmarks.py +++ b/qutebrowser/browser/urlmarks.py @@ -74,6 +74,8 @@ class UrlMarkManager(QObject): changed = pyqtSignal() + _lineparser: lineparser.LineParser + def __init__(self, parent=None): """Initialize and read quickmarks.""" super().__init__(parent) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 9ec29ce07..344292166 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -19,7 +19,7 @@ """Generic web element related code.""" -from typing import cast, TYPE_CHECKING, Iterator, Optional, Set, Union +from typing import cast, TYPE_CHECKING, Iterator, Optional, Set, Union, Dict import collections.abc from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer, QRect, QPoint @@ -132,6 +132,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a """Dispatch an event to the element. Args: + event: The name of the event. bubbles: Whether this event should bubble. cancelable: Whether this event can be cancelled. composed: Whether the event will trigger listeners outside of a @@ -160,9 +161,6 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a def is_content_editable(self) -> bool: """Check if an element has a contenteditable attribute. - Args: - elem: The QWebElement to check. - Return: True if the element has a contenteditable attribute, False otherwise. @@ -233,6 +231,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a 'span': ['cm-'], # Jupyter Notebook } relevant_classes = classes[self.tag_name()] + # pylint: disable=consider-using-any-or-all for klass in self.classes(): if any(klass.strip().startswith(e) for e in relevant_classes): return True @@ -346,18 +345,19 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a log.webelem.debug("Sending fake click to {!r} at position {} with " "target {}".format(self, pos, click_target)) - target_modifiers = { - usertypes.ClickTarget.normal: Qt.NoModifier, + target_modifiers: Dict[usertypes.ClickTarget, Qt.KeyboardModifiers] = { + usertypes.ClickTarget.normal: cast(Qt.KeyboardModifiers, Qt.NoModifier), usertypes.ClickTarget.window: Qt.AltModifier | Qt.ShiftModifier, - usertypes.ClickTarget.tab: Qt.ControlModifier, - usertypes.ClickTarget.tab_bg: Qt.ControlModifier, + usertypes.ClickTarget.tab: cast(Qt.KeyboardModifiers, Qt.ControlModifier), + usertypes.ClickTarget.tab_bg: + cast(Qt.KeyboardModifiers, Qt.ControlModifier), } if config.val.tabs.background: target_modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier else: target_modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier - modifiers = cast(Qt.KeyboardModifiers, target_modifiers[click_target]) + modifiers = target_modifiers[click_target] events = [ QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton, Qt.NoModifier), diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index 6b26157e6..3571bb24d 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -65,7 +65,10 @@ if TYPE_CHECKING: from qutebrowser.config import config from qutebrowser.misc import objects -from qutebrowser.utils import qtutils, log, utils, debug, message, version +from qutebrowser.utils import ( + qtutils, log, utils, debug, message, version, objreg, resources, +) +from qutebrowser.qt import sip bridge: Optional['NotificationBridgePresenter'] = None @@ -275,9 +278,6 @@ class NotificationBridgePresenter(QObject): log.misc.debug("Adapter vanished, bailing out") # type: ignore[unreachable] return - if notification_id <= 0: - raise Error(f"Got invalid notification id {notification_id}") - if replaces_id is None: if notification_id in self._active_notifications: raise Error(f"Got duplicate id {notification_id}") @@ -285,7 +285,7 @@ class NotificationBridgePresenter(QObject): qt_notification.show() self._active_notifications[notification_id] = qt_notification - qt_notification.closed.connect( # type: ignore[attr-defined] + qt_notification.closed.connect( functools.partial(self._adapter.on_web_closed, notification_id)) def _find_replaces_id( @@ -367,6 +367,17 @@ class NotificationBridgePresenter(QObject): # https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042918.html log.misc.debug(f"Ignoring click request for notification {notification_id} " "due to PyQt bug") + return + self._focus_first_matching_tab(notification) + + def _focus_first_matching_tab(self, notification: "QWebEngineNotification") -> None: + for win_id in objreg.window_registry: + tabbedbrowser = objreg.get("tabbed-browser", window=win_id, scope="window") + for idx, tab in enumerate(tabbedbrowser.widgets()): + if tab.url().matches(notification.origin(), QUrl.RemovePath): + tabbedbrowser.widget.setCurrentIndex(idx) + return + log.misc.debug(f"No matching tab found for {notification.origin()}") def _drop_adapter(self) -> None: """Drop the currently active adapter (if any). @@ -477,7 +488,9 @@ class SystrayNotificationAdapter(AbstractNotificationAdapter): @pyqtSlot(int) def on_web_closed(self, notification_id: int) -> None: assert notification_id == self.NOTIFICATION_ID, notification_id - self._systray.hide() + if not sip.isdeleted(self._systray): + # This can get called during shutdown + self._systray.hide() class MessagesNotificationAdapter(AbstractNotificationAdapter): @@ -608,17 +621,19 @@ class HerbeNotificationAdapter(AbstractNotificationAdapter): so there's no point. """ if status == QProcess.CrashExit: - return - - if code == 0: + pass + elif code == 0: self.click_id.emit(pid) elif code == 2: - self.close_id.emit(pid) + pass else: proc = self.sender() + assert isinstance(proc, QProcess), proc stderr = proc.readAllStandardError() raise Error(f'herbe exited with status {code}: {stderr}') + self.close_id.emit(pid) + @pyqtSlot(QProcess.ProcessError) def _on_error(self, error: QProcess.ProcessError) -> None: if error == QProcess.Crashed: @@ -651,6 +666,7 @@ class _ServerQuirks: skip_capabilities: bool = False wrong_replaces_id: bool = False no_padded_images: bool = False + wrong_closes_type: bool = False @dataclasses.dataclass @@ -712,10 +728,23 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): # https://github.com/KDE/plasma-workspace/blob/v5.21.4/libnotificationmanager/server_p.cpp#L227-L237 # Created too many similar notifications in quick succession "org.freedesktop.Notifications.Error.ExcessNotificationGeneration", + + # From https://crashes.qutebrowser.org/view/b8c9838a + # Process org.freedesktop.Notifications received signal 5 + # probably when notification daemon crashes? + "org.freedesktop.DBus.Error.Spawn.ChildSignaled", + + # https://crashes.qutebrowser.org/view/f76f58ae + # Process org.freedesktop.Notifications exited with status 1 + "org.freedesktop.DBus.Error.Spawn.ChildExited", + + # https://crashes.qutebrowser.org/view/8889d0b5 + # Could not activate remote peer. + "org.freedesktop.DBus.Error.NameHasNoOwner", } def __init__(self, parent: QObject = None) -> None: - super().__init__(bridge) + super().__init__(parent) assert _notifications_supported() if utils.is_windows: @@ -737,8 +766,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): QDBusServiceWatcher.WatchForUnregistration, self, ) - self._watcher.serviceUnregistered.connect( # type: ignore[attr-defined] - self._on_service_unregistered) + self._watcher.serviceUnregistered.connect(self._on_service_unregistered) test_service = 'test-notification-service' in objects.debug_flags service = self.TEST_SERVICE if test_service else self.SERVICE @@ -837,12 +865,20 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): wrong_replaces_id=True, ) elif (name, vendor) == ("Raven", "Budgie Desktop Developers"): + # Before refactor return _ServerQuirks( # https://github.com/solus-project/budgie-desktop/issues/2114 escape_title=True, # https://github.com/solus-project/budgie-desktop/issues/2115 wrong_replaces_id=True, ) + elif (name, vendor) == ( + "Budgie Notification Server", "Budgie Desktop Developers"): + # After refactor: https://github.com/BuddiesOfBudgie/budgie-desktop/pull/36 + return _ServerQuirks( + # https://github.com/BuddiesOfBudgie/budgie-desktop/issues/118 + wrong_closes_type=True, + ) return None def _get_server_info(self) -> None: @@ -947,8 +983,8 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): icon = qt_notification.icon() if icon.isNull(): - filename = ':/icons/qutebrowser-64x64.png' - icon = QImage(filename) + filename = 'icons/qutebrowser-64x64.png' + icon = QImage.fromData(resources.read_file_binary(filename)) key = self._quirks.icon_key or "image-data" data = self._convert_image(icon) @@ -986,6 +1022,9 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): else: log.misc.error(msg) + if notification_id <= 0: + self.error.emit(f"Got invalid notification id {notification_id}") + return notification_id def _convert_image(self, qimage: QImage) -> Optional[QDBusArgument]: @@ -1064,7 +1103,13 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): @pyqtSlot(QDBusMessage) def _handle_close(self, msg: QDBusMessage) -> None: """Handle NotificationClosed from DBus.""" - self._verify_message(msg, "uu", QDBusMessage.SignalMessage) + try: + self._verify_message(msg, "uu", QDBusMessage.SignalMessage) + except Error: + if not self._quirks.wrong_closes_type: + raise + self._verify_message(msg, "ui", QDBusMessage.SignalMessage) + notification_id, _close_reason = msg.arguments() self.close_id.emit(notification_id) diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index fc7ed8ca2..a96f49d6b 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -44,10 +44,8 @@ class DownloadItem(downloads.AbstractDownloadItem): parent: QObject = None) -> None: super().__init__(manager=manager, parent=manager) self._qt_item = qt_item - qt_item.downloadProgress.connect( # type: ignore[attr-defined] - self.stats.on_download_progress) - qt_item.stateChanged.connect( # type: ignore[attr-defined] - self._on_state_changed) + qt_item.downloadProgress.connect(self.stats.on_download_progress) + qt_item.stateChanged.connect(self._on_state_changed) # Ensure wrapped qt_item is deleted manually when the wrapper object # is deleted. See https://github.com/qutebrowser/qutebrowser/issues/3373 @@ -92,7 +90,7 @@ class DownloadItem(downloads.AbstractDownloadItem): def _do_die(self): progress_signal = self._qt_item.downloadProgress - progress_signal.disconnect() # type: ignore[attr-defined] + progress_signal.disconnect() if self._qt_item.state() != QWebEngineDownloadItem.DownloadInterrupted: self._qt_item.cancel() @@ -119,6 +117,10 @@ class DownloadItem(downloads.AbstractDownloadItem): def url(self) -> QUrl: return self._qt_item.url() + def origin(self) -> QUrl: + page = self._qt_item.page() + return page.url() if page else QUrl() + def _set_fileobj(self, fileobj, *, autoclose=True): raise downloads.UnsupportedOperationError @@ -251,7 +253,6 @@ class DownloadManager(downloads.AbstractDownloadManager): qt_filename = os.path.basename(qt_item.path()) # FIXME use 5.14 API mime_type = qt_item.mimeType() url = qt_item.url() - origin = qt_item.page().url() if qt_item.page() else QUrl() # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-90355 if version.qtwebengine_versions().webengine >= utils.VersionNumber(5, 15, 3): @@ -293,9 +294,7 @@ class DownloadManager(downloads.AbstractDownloadManager): download.set_target(target) return - if url.scheme() == "file" and origin.isValid() and origin.scheme() == "file": - utils.open_file(url.toLocalFile()) - qt_item.cancel() + if download.cancel_for_origin(): return # Ask the user for a filename - needs to be blocking! diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 5d4c6ad9a..75b7a51ba 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -37,6 +37,8 @@ class WebEngineElement(webelem.AbstractWebElement): """A web element for QtWebEngine, using JS under the hood.""" + _tab: "webenginetab.WebEngineTab" + def __init__(self, js_dict: Dict[str, Any], tab: 'webenginetab.WebEngineTab') -> None: super().__init__(tab) @@ -248,7 +250,7 @@ class WebEngineElement(webelem.AbstractWebElement): # (it does so with a 0ms QTimer...) # This is also used in Qt's tests: # https://github.com/qt/qtwebengine/commit/5e572e88efa7ba7c2b9138ec19e606d3e345ac90 - QApplication.processEvents( # type: ignore[call-overload] + QApplication.processEvents( QEventLoop.ExcludeSocketNotifiers | QEventLoop.ExcludeUserInputEvents) diff --git a/qutebrowser/browser/webengine/webengineinspector.py b/qutebrowser/browser/webengine/webengineinspector.py index ae31c0bee..58babc70c 100644 --- a/qutebrowser/browser/webengine/webengineinspector.py +++ b/qutebrowser/browser/webengine/webengineinspector.py @@ -49,13 +49,17 @@ class WebEngineInspectorView(QWebEngineView): See WebEngineView.createWindow for details. """ - return self.page().inspectedPage().view().createWindow(wintype) + view = self.page().inspectedPage().view() + assert isinstance(view, QWebEngineView), view + return view.createWindow(wintype) class WebEngineInspector(inspector.AbstractWebInspector): """A web inspector for QtWebEngine with Qt API support.""" + _widget: WebEngineInspectorView + def __init__(self, splitter: miscwidgets.InspectorSplitter, win_id: int, parent: QWidget = None) -> None: @@ -66,8 +70,7 @@ class WebEngineInspector(inspector.AbstractWebInspector): self._settings = webenginesettings.WebEngineSettings(view.settings()) self._set_widget(view) page = view.page() - page.windowCloseRequested.connect( # type: ignore[attr-defined] - self._on_window_close_requested) + page.windowCloseRequested.connect(self._on_window_close_requested) def _on_window_close_requested(self) -> None: """Called when the 'x' was clicked in the devtools.""" @@ -96,7 +99,7 @@ class WebEngineInspector(inspector.AbstractWebInspector): "please install the qt5-qtwebengine-devtools " "Fedora package.") - def inspect(self, page: QWebEnginePage) -> None: # type: ignore[override] + def inspect(self, page: QWebEnginePage) -> None: inspector_page = self._widget.page() inspector_page.setInspectedPage(page) self._settings.update_for_url(inspector_page.requestedUrl()) diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py index 64361f7c4..9e073951a 100644 --- a/qutebrowser/browser/webengine/webenginequtescheme.py +++ b/qutebrowser/browser/webengine/webenginequtescheme.py @@ -138,6 +138,6 @@ def init(): assert not QWebEngineUrlScheme.schemeByName(b'qute').name() scheme = QWebEngineUrlScheme(b'qute') scheme.setFlags( - QWebEngineUrlScheme.LocalScheme | # type: ignore[arg-type] + QWebEngineUrlScheme.LocalScheme | QWebEngineUrlScheme.LocalAccessAllowed) QWebEngineUrlScheme.registerScheme(scheme) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index d20a92014..ad374ed9b 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -117,8 +117,6 @@ class WebEngineSettings(websettings.AbstractSettings): Attr(QWebEngineSettings.JavascriptEnabled), 'content.javascript.can_open_tabs_automatically': Attr(QWebEngineSettings.JavascriptCanOpenWindows), - 'content.javascript.can_access_clipboard': - Attr(QWebEngineSettings.JavascriptCanAccessClipboard), 'content.plugins': Attr(QWebEngineSettings.PluginsEnabled), 'content.hyperlink_auditing': @@ -199,26 +197,47 @@ class WebEngineSettings(websettings.AbstractSettings): QWebEngineSettings.FantasyFont: QFont.Fantasy, } - def set_unknown_url_scheme_policy( - self, policy: Union[str, usertypes.Unset]) -> bool: - """Set the UnknownUrlSchemePolicy to use. + _JS_CLIPBOARD_SETTINGS = { + 'none': { + QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard: False, + QWebEngineSettings.WebAttribute.JavascriptCanPaste: False, + }, + 'access': { + QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard: True, + QWebEngineSettings.WebAttribute.JavascriptCanPaste: False, + }, + 'access-paste': { + QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard: True, + QWebEngineSettings.WebAttribute.JavascriptCanPaste: True, + }, + } - Return: - True if there was a change, False otherwise. - """ - old_value = self._settings.unknownUrlSchemePolicy() + def set_unknown_url_scheme_policy( + self, policy: Union[str, usertypes.Unset]) -> None: + """Set the UnknownUrlSchemePolicy to use.""" if isinstance(policy, usertypes.Unset): self._settings.resetUnknownUrlSchemePolicy() - new_value = self._settings.unknownUrlSchemePolicy() else: new_value = self._UNKNOWN_URL_SCHEME_POLICY[policy] self._settings.setUnknownUrlSchemePolicy(new_value) - return old_value != new_value + + def _set_js_clipboard(self, value: Union[str, usertypes.Unset]) -> None: + attr_access = QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard + attr_paste = QWebEngineSettings.WebAttribute.JavascriptCanPaste + + if isinstance(value, usertypes.Unset): + self._settings.resetAttribute(attr_access) + self._settings.resetAttribute(attr_paste) + else: + for attr, attr_val in self._JS_CLIPBOARD_SETTINGS[value].items(): + self._settings.setAttribute(attr, attr_val) def _update_setting(self, setting, value): if setting == 'content.unknown_url_scheme_policy': - return self.set_unknown_url_scheme_policy(value) - return super()._update_setting(setting, value) + self.set_unknown_url_scheme_policy(value) + elif setting == 'content.javascript.clipboard': + self._set_js_clipboard(value) + super()._update_setting(setting, value) def init_settings(self): super().init_settings() @@ -332,9 +351,9 @@ class ProfileSetter: def _update_settings(option): """Update global settings when qwebsettings changed.""" _global_settings.update_setting(option) - default_profile.setter.update_setting(option) + default_profile.setter.update_setting(option) # type: ignore[attr-defined] if private_profile: - private_profile.setter.update_setting(option) + private_profile.setter.update_setting(option) # type: ignore[attr-defined] def _init_user_agent_str(ua): @@ -352,8 +371,9 @@ def _init_profile(profile: QWebEngineProfile) -> None: This currently only contains the steps which are shared between a private and a non-private profile (at the moment, only the default profile). """ + # FIXME:mypy subclass QWebEngineProfile instead? profile.setter = ProfileSetter(profile) # type: ignore[attr-defined] - profile.setter.init_profile() + profile.setter.init_profile() # type: ignore[attr-defined] _qute_scheme_handler.install(profile) _req_interceptor.install(profile) @@ -431,11 +451,7 @@ def _init_site_specific_quirks(): "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/99 " "Safari/537.36") - edge_ua = ("Mozilla/5.0 ({os_info}) " - "AppleWebKit/{webkit_version} (KHTML, like Gecko) " - "{upstream_browser_key}/{upstream_browser_version} " - "Safari/{webkit_version} " - "Edg/{upstream_browser_version}") + firefox_ua = "Mozilla/5.0 ({os_info}; rv:90.0) Gecko/20100101 Firefox/90.0" user_agents = [ # Needed to avoid a ""WhatsApp works with Google Chrome 36+" error @@ -447,7 +463,7 @@ def _init_site_specific_quirks(): # Needed to avoid a "you're using a browser [...] that doesn't allow us # to keep your account secure" error. # https://github.com/qutebrowser/qutebrowser/issues/5182 - ("ua-google", 'https://accounts.google.com/*', edge_ua), + ("ua-google", 'https://accounts.google.com/*', firefox_ua), # Needed because Slack adds an error which prevents using it relatively # aggressively, despite things actually working fine. diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index ace23d14a..0a2333afc 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -26,10 +26,11 @@ import re import html as html_utils from typing import cast, Union, Optional -from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl, QObject +from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QTimer, QUrl, + QObject) from PyQt5.QtNetwork import QAuthenticator -from PyQt5.QtWidgets import QWidget -from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineHistory +from PyQt5.QtWebEngineWidgets import (QWebEnginePage, QWebEngineView, QWebEngineScript, + QWebEngineHistory) from qutebrowser.config import config from qutebrowser.browser import browsertab, eventfilter, shared, webelem, greasemonkey @@ -56,6 +57,8 @@ class WebEngineAction(browsertab.AbstractAction): """QtWebEngine implementations related to web actions.""" + _widget: webview.WebEngineView + action_class = QWebEnginePage action_base = QWebEnginePage.WebAction @@ -78,6 +81,8 @@ class WebEnginePrinting(browsertab.AbstractPrinting): """QtWebEngine implementations related to printing.""" + _widget: webview.WebEngineView + def check_pdf_support(self): pass @@ -88,9 +93,7 @@ class WebEnginePrinting(browsertab.AbstractPrinting): def to_pdf(self, filename): self._widget.page().printToPdf(filename) - def to_printer(self, printer, callback=None): - if callback is None: - callback = lambda _ok: None + def to_printer(self, printer, callback=lambda ok: None): self._widget.page().print(printer, callback) @@ -190,6 +193,8 @@ class WebEngineSearch(browsertab.AbstractSearch): back yet. """ + _widget: webview.WebEngineView + def __init__(self, tab, parent=None): super().__init__(tab, parent) self._flags = self._empty_flags() @@ -198,7 +203,15 @@ class WebEngineSearch(browsertab.AbstractSearch): self._wrap_handler = _WebEngineSearchWrapHandler() def _empty_flags(self): - return QWebEnginePage.FindFlags(0) # type: ignore[call-overload] + return QWebEnginePage.FindFlags(0) + + def _args_to_flags(self, reverse, ignore_case): + flags = self._empty_flags() + if self._is_case_sensitive(ignore_case): + flags |= QWebEnginePage.FindCaseSensitively + if reverse: + flags |= QWebEnginePage.FindBackward + return flags def connect_signals(self): self._wrap_handler.connect_signal(self._widget.page()) @@ -246,17 +259,14 @@ class WebEngineSearch(browsertab.AbstractSearch): # Don't go to next entry on duplicate search if self.text == text and self.search_displayed: log.webview.debug("Ignoring duplicate search request" - " for {}".format(text)) + " for {}, but resetting flags".format(text)) + self._flags = self._args_to_flags(reverse, ignore_case) return self.text = text - self._flags = self._empty_flags() + self._flags = self._args_to_flags(reverse, ignore_case) self._wrap_handler.reset_match_data() self._wrap_handler.flag_wrap = wrap - if self._is_case_sensitive(ignore_case): - self._flags |= QWebEnginePage.FindCaseSensitively - if reverse: - self._flags |= QWebEnginePage.FindBackward self._find(text, self._flags, result_cb, 'search') @@ -269,8 +279,7 @@ class WebEngineSearch(browsertab.AbstractSearch): def prev_result(self, *, result_cb=None): # The int() here makes sure we get a copy of the flags. - flags = QWebEnginePage.FindFlags( - int(self._flags)) # type: ignore[call-overload] + flags = QWebEnginePage.FindFlags(int(self._flags)) if flags & QWebEnginePage.FindBackward: if self._wrap_handler.prevent_wrapping(going_up=False): return @@ -487,6 +496,8 @@ class WebEngineScroller(browsertab.AbstractScroller): """QtWebEngine implementations related to scrolling.""" + _widget: webview.WebEngineView + def __init__(self, tab, parent=None): super().__init__(tab, parent) self._pos_perc = (0, 0) @@ -707,6 +718,8 @@ class WebEngineZoom(browsertab.AbstractZoom): """QtWebEngine implementations related to zooming.""" + _widget: webview.WebEngineView + def _set_factor_internal(self, factor): self._widget.setZoomFactor(factor) @@ -793,17 +806,44 @@ class WebEngineAudio(browsertab.AbstractAudio): If that's the case, we leave it alone. """ + _widget: webview.WebEngineView + def __init__(self, tab, parent=None): super().__init__(tab, parent) self._overridden = False + # Implements the intended two-second delay specified at + # https://doc.qt.io/qt-5/qwebenginepage.html#recentlyAudibleChanged + delay_ms = 2000 + self._silence_timer = QTimer(self) + self._silence_timer.setSingleShot(True) + self._silence_timer.setInterval(delay_ms) + def _connect_signals(self): page = self._widget.page() page.audioMutedChanged.connect(self.muted_changed) - page.recentlyAudibleChanged.connect(self.recently_audible_changed) + page.recentlyAudibleChanged.connect(self._delayed_recently_audible_changed) self._tab.url_changed.connect(self._on_url_changed) config.instance.changed.connect(self._on_config_changed) + # WORKAROUND for recentlyAudibleChanged being emitted without delay from the moment + # that audio is dropped. + def _delayed_recently_audible_changed(self, recently_audible): + timer = self._silence_timer + # Stop any active timer and immediately display [A] if tab is audible, + # otherwise start a timer to update audio field + if recently_audible: + if timer.isActive(): + timer.stop() + self.recently_audible_changed.emit(recently_audible) + else: + # Ignore all subsequent calls while the tab is muted with an active timer + if timer.isActive(): + return + timer.timeout.connect( + functools.partial(self.recently_audible_changed.emit, recently_audible)) + timer.start() + def set_muted(self, muted: bool, override: bool = False) -> None: was_muted = self.is_muted() self._overridden = override @@ -839,6 +879,8 @@ class _WebEnginePermissions(QObject): """Handling of various permission-related signals.""" + _widget: webview.WebEngineView + # Using 0 as WORKAROUND for: # https://www.riverbankcomputing.com/pipermail/pyqt/2019-July/041903.html @@ -867,7 +909,7 @@ class _WebEnginePermissions(QObject): def __init__(self, tab, parent=None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(webview.WebEngineView, None) assert self._options.keys() == self._messages.keys() def connect_signals(self): @@ -1002,10 +1044,12 @@ class _Quirk: class _WebEngineScripts(QObject): + _widget: webview.WebEngineView + def __init__(self, tab, parent=None): super().__init__(parent) self._tab = tab - self._widget = cast(QWidget, None) + self._widget = cast(webview.WebEngineView, None) self._greasemonkey = greasemonkey.gm_manager def connect_signals(self): @@ -1209,6 +1253,8 @@ class WebEngineTabPrivate(browsertab.AbstractTabPrivate): """QtWebEngine-related methods which aren't part of the public API.""" + _widget: webview.WebEngineView + def networkaccessmanager(self): return None @@ -1244,6 +1290,10 @@ class WebEngineTab(browsertab.AbstractTab): abort_questions = pyqtSignal() + _widget: QWebEngineView + search: WebEngineSearch + audio: WebEngineAudio + def __init__(self, *, win_id, mode_manager, private, parent=None): super().__init__(win_id=win_id, mode_manager=mode_manager, @@ -1499,7 +1549,6 @@ class WebEngineTab(browsertab.AbstractTab): displayed. WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66643 - WORKAROUND for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=882805 """ match = re.search(r'"errorCode":"([^"]*)"', html) if match is None: @@ -1508,8 +1557,7 @@ class WebEngineTab(browsertab.AbstractTab): error = match.group(1) log.webview.error("Load error: {}".format(error)) - missing_jst = 'jstProcess(' in html and 'jstProcess=' not in html - if js_enabled and not missing_jst: + if js_enabled: return self._show_error_page(self.url(), error=error) @@ -1656,6 +1704,7 @@ class WebEngineTab(browsertab.AbstractTab): def _connect_signals(self): view = self._widget page = view.page() + assert isinstance(page, webview.WebEnginePage), page page.windowCloseRequested.connect(self.window_close_requested) page.linkHovered.connect(self.link_hovered) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 76ce1a42e..4dd1b2a71 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -70,7 +70,10 @@ class WebEngineView(QWebEngineView): return self.focusProxy() def shutdown(self): - self.page().shutdown() + """Shut down the underlying page.""" + page = self.page() + assert isinstance(page, WebEnginePage), page + page.shutdown() def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. diff --git a/qutebrowser/browser/webkit/http.py b/qutebrowser/browser/webkit/http.py index eacb95679..a38cd358a 100644 --- a/qutebrowser/browser/webkit/http.py +++ b/qutebrowser/browser/webkit/http.py @@ -89,20 +89,31 @@ class ContentDisposition: try: parsed = reg('Content-Disposition', decoded) except IndexError: # pragma: no cover - # WORKAROUND for https://bugs.python.org/issue37491 + # WORKAROUND for https://github.com/python/cpython/issues/81672 # Fixed in Python 3.7.5 and 3.8.0. + # Still getting failures on 3.10 on CI though raise ContentDispositionError("Missing closing quote character") except ValueError: # pragma: no cover - # WORKAROUND for https://bugs.python.org/issue42946 + # WORKAROUND for https://github.com/python/cpython/issues/87112 raise ContentDispositionError("Non-ASCII digit") + except AttributeError: + # WORKAROUND for https://github.com/python/cpython/issues/93010 + raise ContentDispositionError("Section number has an invalid leading 0") if parsed.defects: defects = list(parsed.defects) if defects != [cls._IGNORED_DEFECT]: # type: ignore[comparison-overlap] raise ContentDispositionError(defects) - assert isinstance(parsed, email.headerregistry.ContentDispositionHeader), parsed - return cls(disposition=parsed.content_disposition, params=parsed.params) + # https://github.com/python/mypy/issues/12314 + assert isinstance( + parsed, # type: ignore[unreachable] + email.headerregistry.ContentDispositionHeader, + ), parsed + return cls( # type: ignore[unreachable] + disposition=parsed.content_disposition, + params=parsed.params, + ) def filename(self): """The filename from the Content-Disposition header or None. @@ -143,7 +154,7 @@ def parse_content_disposition(reply): """ is_inline = True filename = None - content_disposition_header = 'Content-Disposition'.encode('iso-8859-1') + content_disposition_header = b'Content-Disposition' # First check if the Content-Disposition header has a filename # attribute. if reply.hasRawHeader(content_disposition_header): @@ -151,7 +162,7 @@ def parse_content_disposition(reply): # os.path.basename later. try: value = bytes(reply.rawHeader(content_disposition_header)) - log.network.debug("Parsing Content-Disposition: {value!r}") + log.network.debug(f"Parsing Content-Disposition: {value!r}") content_disposition = ContentDisposition.parse(value) filename = content_disposition.filename() except ContentDispositionError as e: diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index c97c91d66..83eaabda7 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -25,7 +25,7 @@ import dataclasses from typing import TYPE_CHECKING, Dict, MutableMapping, Optional, Set from PyQt5.QtCore import pyqtSlot, pyqtSignal, QUrl, QByteArray -from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslSocket, +from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslConfiguration, QNetworkProxy) from qutebrowser.config import config @@ -103,7 +103,8 @@ def _is_secure_cipher(cipher): def init(): """Disable insecure SSL ciphers on old Qt versions.""" - default_ciphers = QSslSocket.defaultCiphers() + sslconfig = QSslConfiguration.defaultConfiguration() + default_ciphers = sslconfig.ciphers() log.init.vdebug( # type: ignore[attr-defined] "Default Qt ciphers: {}".format( ', '.join(c.name() for c in default_ciphers))) @@ -119,7 +120,7 @@ def init(): if bad_ciphers: log.init.debug("Disabling bad ciphers: {}".format( ', '.join(c.name() for c in bad_ciphers))) - QSslSocket.setDefaultCiphers(good_ciphers) + sslconfig.setCiphers(good_ciphers) _SavedErrorsType = MutableMapping[ @@ -379,10 +380,10 @@ class NetworkManager(QNetworkAccessManager): if referer_header_conf == 'never': # Note: using ''.encode('ascii') sends a header with no value, # instead of no header at all - req.setRawHeader('Referer'.encode('ascii'), QByteArray()) + req.setRawHeader(b'Referer', QByteArray()) elif (referer_header_conf == 'same-domain' and not urlutils.same_domain(req.url(), current_url)): - req.setRawHeader('Referer'.encode('ascii'), QByteArray()) + req.setRawHeader(b'Referer', QByteArray()) # If refer_header_conf is set to 'always', we leave the header # alone as QtWebKit did set it. except urlutils.InvalidUrlError: diff --git a/qutebrowser/browser/webkit/network/networkreply.py b/qutebrowser/browser/webkit/network/networkreply.py index c1ead3209..d5c724d63 100644 --- a/qutebrowser/browser/webkit/network/networkreply.py +++ b/qutebrowser/browser/webkit/network/networkreply.py @@ -59,15 +59,9 @@ class FixedDataNetworkReply(QNetworkReply): # For some reason, a segfault will be triggered if these lambdas aren't # there. # pylint: disable=unnecessary-lambda - QTimer.singleShot( - 0, - lambda: self.metaDataChanged.emit()) # type: ignore[attr-defined] - QTimer.singleShot( - 0, - lambda: self.readyRead.emit()) # type: ignore[attr-defined] - QTimer.singleShot( - 0, - lambda: self.finished.emit()) # type: ignore[attr-defined] + QTimer.singleShot(0, lambda: self.metaDataChanged.emit()) + QTimer.singleShot(0, lambda: self.readyRead.emit()) + QTimer.singleShot(0, lambda: self.finished.emit()) @pyqtSlot() def abort(self): @@ -122,10 +116,8 @@ class ErrorNetworkReply(QNetworkReply): # the device to avoid getting a warning. self.setOpenMode(QIODevice.ReadOnly) self.setError(error, errorstring) - QTimer.singleShot(0, lambda: - self.error.emit(error)) # type: ignore[attr-defined] - QTimer.singleShot(0, lambda: - self.finished.emit()) # type: ignore[attr-defined] + QTimer.singleShot(0, lambda: self.error.emit(error)) + QTimer.singleShot(0, lambda: self.finished.emit()) def abort(self): """Do nothing since it's a fake reply.""" @@ -152,8 +144,7 @@ class RedirectNetworkReply(QNetworkReply): def __init__(self, new_url, parent=None): super().__init__(parent) self.setAttribute(QNetworkRequest.RedirectionTargetAttribute, new_url) - QTimer.singleShot(0, lambda: - self.finished.emit()) # type: ignore[attr-defined] + QTimer.singleShot(0, lambda: self.finished.emit()) def abort(self): """Called when there's e.g. a redirection limit.""" diff --git a/qutebrowser/browser/webkit/webkithistory.py b/qutebrowser/browser/webkit/webkithistory.py index 4149bda88..6ffe65193 100644 --- a/qutebrowser/browser/webkit/webkithistory.py +++ b/qutebrowser/browser/webkit/webkithistory.py @@ -44,7 +44,7 @@ class WebHistoryInterface(QWebHistoryInterface): """Required for a QWebHistoryInterface impl, obsoleted by add_url.""" @debugcachestats.register(name='history') - @functools.lru_cache(maxsize=32768) + @functools.lru_cache(maxsize=32768) # noqa: B019 def historyContains(self, url_string): """Called by WebKit to determine if a URL is contained in the history. diff --git a/qutebrowser/browser/webkit/webkitinspector.py b/qutebrowser/browser/webkit/webkitinspector.py index 57bcd40ea..4d9785cbe 100644 --- a/qutebrowser/browser/webkit/webkitinspector.py +++ b/qutebrowser/browser/webkit/webkitinspector.py @@ -38,7 +38,7 @@ class WebKitInspector(inspector.AbstractWebInspector): qwebinspector = QWebInspector() self._set_widget(qwebinspector) - def inspect(self, page: QWebPage) -> None: # type: ignore[override] + def inspect(self, page: QWebPage) -> None: settings = QWebSettings.globalSettings() settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) self._widget.setPage(page) diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index cac6236ce..1348c849b 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -57,8 +57,9 @@ class WebKitSettings(websettings.AbstractSettings): Attr(QWebSettings.JavascriptCanOpenWindows), 'content.javascript.can_close_tabs': Attr(QWebSettings.JavascriptCanCloseWindows), - 'content.javascript.can_access_clipboard': - Attr(QWebSettings.JavascriptCanAccessClipboard), + 'content.javascript.clipboard': + Attr(QWebSettings.JavascriptCanAccessClipboard, + converter=lambda val: val != "none"), 'content.plugins': Attr(QWebSettings.PluginsEnabled), 'content.webgl': diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index df3491ec2..24d232c9c 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -32,8 +32,9 @@ from PyQt5.QtWebKit import QWebSettings, QWebHistory, QWebElement from PyQt5.QtPrintSupport import QPrinter from qutebrowser.browser import browsertab, shared -from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem, +from qutebrowser.browser.webkit import (webview, webpage, tabhistory, webkitelem, webkitsettings, webkitinspector) +from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.utils import qtutils, usertypes, utils, log, debug, resources from qutebrowser.keyinput import modeman from qutebrowser.qt import sip @@ -46,6 +47,8 @@ class WebKitAction(browsertab.AbstractAction): action_class = QWebPage action_base = QWebPage.WebAction + _widget: webview.WebView + def exit_fullscreen(self): raise browsertab.UnsupportedOperationError @@ -69,7 +72,7 @@ class WebKitAction(browsertab.AbstractAction): 'Unselect': QWebPage.ToggleVideoFullscreen + 2, } if name in new_actions: - self._widget.triggerPageAction(new_actions[name]) + self._widget.triggerPageAction(new_actions[name]) # type: ignore[arg-type] return super().run_string(name) @@ -79,6 +82,8 @@ class WebKitPrinting(browsertab.AbstractPrinting): """QtWebKit implementations related to printing.""" + _widget: webview.WebView + def check_pdf_support(self): pass @@ -101,6 +106,8 @@ class WebKitSearch(browsertab.AbstractSearch): """QtWebKit implementations related to searching on the page.""" + _widget: webview.WebView + def __init__(self, tab, parent=None): super().__init__(tab, parent) self._flags = self._empty_flags() @@ -108,6 +115,16 @@ class WebKitSearch(browsertab.AbstractSearch): def _empty_flags(self): return QWebPage.FindFlags(0) # type: ignore[call-overload] + def _args_to_flags(self, reverse, ignore_case, wrap): + flags = self._empty_flags() + if self._is_case_sensitive(ignore_case): + flags |= QWebPage.FindCaseSensitively + if reverse: + flags |= QWebPage.FindBackward + if wrap: + flags |= QWebPage.FindWrapsAroundDocument + return flags + def _call_cb(self, callback, found, text, flags, caller): """Call the given callback if it's non-None. @@ -143,14 +160,16 @@ class WebKitSearch(browsertab.AbstractSearch): self.search_displayed = False # We first clear the marked text, then the highlights self._widget.findText('') - self._widget.findText('', QWebPage.HighlightAllOccurrences) + self._widget.findText( + '', QWebPage.HighlightAllOccurrences) # type: ignore[arg-type] def search(self, text, *, ignore_case=usertypes.IgnoreCase.never, reverse=False, wrap=True, result_cb=None): # Don't go to next entry on duplicate search if self.text == text and self.search_displayed: log.webview.debug("Ignoring duplicate search request" - " for {}".format(text)) + " for {}, but resetting flags".format(text)) + self._flags = self._args_to_flags(reverse, ignore_case, wrap) return # Clear old search results, this is done automatically on QtWebEngine. @@ -158,13 +177,7 @@ class WebKitSearch(browsertab.AbstractSearch): self.text = text self.search_displayed = True - self._flags = self._empty_flags() - if self._is_case_sensitive(ignore_case): - self._flags |= QWebPage.FindCaseSensitively - if reverse: - self._flags |= QWebPage.FindBackward - if wrap: - self._flags |= QWebPage.FindWrapsAroundDocument + self._flags = self._args_to_flags(reverse, ignore_case, wrap) # We actually search *twice* - once to highlight everything, then again # to get a mark so we can navigate. found = self._widget.findText(text, self._flags) @@ -174,7 +187,7 @@ class WebKitSearch(browsertab.AbstractSearch): def next_result(self, *, result_cb=None): self.search_displayed = True - found = self._widget.findText(self.text, self._flags) + found = self._widget.findText(self.text, self._flags) # type: ignore[arg-type] self._call_cb(result_cb, found, self.text, self._flags, 'next_result') def prev_result(self, *, result_cb=None): @@ -186,7 +199,7 @@ class WebKitSearch(browsertab.AbstractSearch): flags &= ~QWebPage.FindBackward else: flags |= QWebPage.FindBackward - found = self._widget.findText(self.text, flags) + found = self._widget.findText(self.text, flags) # type: ignore[arg-type] self._call_cb(result_cb, found, self.text, flags, 'prev_result') @@ -194,6 +207,8 @@ class WebKitCaret(browsertab.AbstractCaret): """QtWebKit implementations related to moving the cursor/selection.""" + _widget: webview.WebView + def __init__(self, tab: 'WebKitTab', mode_manager: modeman.ModeManager, @@ -510,6 +525,8 @@ class WebKitZoom(browsertab.AbstractZoom): """QtWebKit implementations related to zooming.""" + _widget: webview.WebView + def _set_factor_internal(self, factor): self._widget.setZoomFactor(factor) @@ -520,6 +537,8 @@ class WebKitScroller(browsertab.AbstractScroller): # FIXME:qtwebengine When to use the main frame, when the current one? + _widget: webview.WebView + def pos_px(self): return self._widget.page().mainFrame().scrollPosition() @@ -619,6 +638,8 @@ class WebKitHistoryPrivate(browsertab.AbstractHistoryPrivate): """History-related methods which are not part of the extension API.""" + _history: QWebHistory + def __init__(self, tab: 'WebKitTab') -> None: self._tab = tab self._history = cast(QWebHistory, None) @@ -690,6 +711,7 @@ class WebKitElements(browsertab.AbstractElements): """QtWebKit implementations related to elements on the page.""" _tab: 'WebKitTab' + _widget: webview.WebView def find_css(self, selector, callback, error_cb, *, only_visible=False): utils.unused(error_cb) @@ -725,7 +747,7 @@ class WebKitElements(browsertab.AbstractElements): self.find_css('#' + elem_id, find_id_cb, error_cb=lambda exc: None) def find_focused(self, callback): - frame = self._widget.page().currentFrame() + frame = cast(Optional[QWebFrame], self._widget.page().currentFrame()) if frame is None: callback(None) return @@ -739,7 +761,7 @@ class WebKitElements(browsertab.AbstractElements): def find_at_pos(self, pos, callback): assert pos.x() >= 0 assert pos.y() >= 0 - frame = self._widget.page().frameAt(pos) + frame = cast(Optional[QWebFrame], self._widget.page().frameAt(pos)) if frame is None: # This happens when we click inside the webview, but not actually # on the QWebPage - for example when clicking the scrollbar @@ -791,6 +813,8 @@ class WebKitTabPrivate(browsertab.AbstractTabPrivate): """QtWebKit-related methods which aren't part of the public API.""" + _widget: webview.WebView + def networkaccessmanager(self): return self._widget.page().networkAccessManager() @@ -816,6 +840,8 @@ class WebKitTab(browsertab.AbstractTab): """A QtWebKit tab in the browser.""" + _widget: webview.WebView + def __init__(self, *, win_id, mode_manager, private, parent=None): super().__init__(win_id=win_id, mode_manager=mode_manager, @@ -907,6 +933,7 @@ class WebKitTab(browsertab.AbstractTab): def _on_load_started(self): super()._on_load_started() nam = self._widget.page().networkAccessManager() + assert isinstance(nam, networkmanager.NetworkManager), nam nam.netrc_used = False # Make sure the icon is cleared when navigating to a page without one. self.icon_changed.emit(QIcon()) @@ -924,7 +951,9 @@ class WebKitTab(browsertab.AbstractTab): when using error pages... See https://github.com/qutebrowser/qutebrowser/issues/84 """ - self._on_load_finished(not self._widget.page().error_occurred) + page = self._widget.page() + assert isinstance(page, webpage.BrowserPage), page + self._on_load_finished(not page.error_occurred) @pyqtSlot() def _on_webkit_icon_changed(self): @@ -979,18 +1008,30 @@ class WebKitTab(browsertab.AbstractTab): view = self._widget page = view.page() frame = page.mainFrame() - page.windowCloseRequested.connect(self.window_close_requested) - page.linkHovered.connect(self.link_hovered) - page.loadProgress.connect(self._on_load_progress) - frame.loadStarted.connect(self._on_load_started) + page.windowCloseRequested.connect( # type: ignore[attr-defined] + self.window_close_requested) + page.linkHovered.connect( # type: ignore[attr-defined] + self.link_hovered) + page.loadProgress.connect( # type: ignore[attr-defined] + self._on_load_progress) + frame.loadStarted.connect( # type: ignore[attr-defined] + self._on_load_started) view.scroll_pos_changed.connect(self.scroller.perc_changed) - view.titleChanged.connect(self.title_changed) - view.urlChanged.connect(self._on_url_changed) + view.titleChanged.connect( # type: ignore[attr-defined] + self.title_changed) + view.urlChanged.connect( # type: ignore[attr-defined] + self._on_url_changed) view.shutting_down.connect(self.shutting_down) page.networkAccessManager().sslErrors.connect(self._on_ssl_errors) - frame.loadFinished.connect(self._on_frame_load_finished) - view.iconChanged.connect(self._on_webkit_icon_changed) - page.frameCreated.connect(self._on_frame_created) - frame.contentsSizeChanged.connect(self._on_contents_size_changed) - frame.initialLayoutCompleted.connect(self._on_history_trigger) - page.navigation_request.connect(self._on_navigation_request) + frame.loadFinished.connect( # type: ignore[attr-defined] + self._on_frame_load_finished) + view.iconChanged.connect( # type: ignore[attr-defined] + self._on_webkit_icon_changed) + page.frameCreated.connect( # type: ignore[attr-defined] + self._on_frame_created) + frame.contentsSizeChanged.connect( # type: ignore[attr-defined] + self._on_contents_size_changed) + frame.initialLayoutCompleted.connect( # type: ignore[attr-defined] + self._on_history_trigger) + page.navigation_request.connect( # type: ignore[attr-defined] + self._on_navigation_request) diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index ddbd78de2..9c7495478 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -21,7 +21,6 @@ import html import functools -from typing import cast from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint from PyQt5.QtGui import QDesktopServices @@ -241,6 +240,7 @@ class BrowserPage(QWebPage): if download_manager.has_downloads_with_nam(nam): nam.setParent(download_manager) else: + assert isinstance(nam, networkmanager.NetworkManager), nam nam.shutdown() def display_content(self, reply, mimetype): @@ -371,11 +371,10 @@ class BrowserPage(QWebPage): self.setFeaturePermission, frame, feature, QWebPage.PermissionDeniedByUser) - url = frame.url().adjusted(cast(QUrl.FormattingOptions, - QUrl.RemoveUserInfo | - QUrl.RemovePath | - QUrl.RemoveQuery | - QUrl.RemoveFragment)) + url = frame.url().adjusted(QUrl.RemoveUserInfo | # type: ignore[operator] + QUrl.RemovePath | + QUrl.RemoveQuery | + QUrl.RemoveFragment) question = shared.feature_permission( url=url, option=options[feature], msg=messages[feature], diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 0242bed0c..be087c855 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -109,7 +109,9 @@ class WebView(QWebView): settings = self.settings() settings.setAttribute(QWebSettings.JavascriptEnabled, False) self.stop() - self.page().shutdown() + page = self.page() + assert isinstance(page, webpage.BrowserPage), page + page.shutdown() def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. @@ -152,9 +154,6 @@ class WebView(QWebView): Args: e: The QPaintEvent. - - Return: - The superclass event return value. """ frame = self.page().mainFrame() new_pos = (frame.scrollBarValue(Qt.Horizontal), @@ -186,9 +185,6 @@ class WebView(QWebView): Args: e: The QShowEvent. - - Return: - The superclass event return value. """ super().showEvent(e) self.page().setVisibilityState(QWebPage.VisibilityStateVisible) @@ -198,9 +194,6 @@ class WebView(QWebView): Args: e: The QHideEvent. - - Return: - The superclass event return value. """ super().hideEvent(e) self.page().setVisibilityState(QWebPage.VisibilityStateHidden) diff --git a/qutebrowser/commands/argparser.py b/qutebrowser/commands/argparser.py index f8f083b72..2a11589f9 100644 --- a/qutebrowser/commands/argparser.py +++ b/qutebrowser/commands/argparser.py @@ -101,7 +101,7 @@ def type_conv(param, typ, value, *, str_choices=None): Args: param: The argparse.Parameter we're checking - types: The allowed type + typ: The allowed type value: The value to convert str_choices: The allowed choices if the type ends up being a string diff --git a/qutebrowser/commands/cmdexc.py b/qutebrowser/commands/cmdexc.py index fdd06537f..50488f1e5 100644 --- a/qutebrowser/commands/cmdexc.py +++ b/qutebrowser/commands/cmdexc.py @@ -22,6 +22,9 @@ Defined here to avoid circular dependency hell. """ +from typing import List +import difflib + class Error(Exception): @@ -32,6 +35,24 @@ class NoSuchCommandError(Error): """Raised when a command isn't found.""" + @classmethod + def for_cmd(cls, cmd: str, all_commands: List[str] = None) -> "NoSuchCommandError": + """Raise an exception for the given command.""" + suffix = '' + if all_commands: + matches = difflib.get_close_matches(cmd, all_commands, n=1) + if matches: + suffix = f' (did you mean :{matches[0]}?)' + return cls(f"{cmd}: no such command{suffix}") + + +class EmptyCommandError(NoSuchCommandError): + + """Raised when no command was given.""" + + def __init__(self): + super().__init__("No command given") + class ArgumentTypeError(Error): diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index 653f551ff..eee5b7cde 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -272,11 +272,10 @@ class Command: if is_bool: kwargs['action'] = 'store_true' + elif arg_info.metavar is not None: + kwargs['metavar'] = arg_info.metavar else: - if arg_info.metavar is not None: - kwargs['metavar'] = arg_info.metavar - else: - kwargs['metavar'] = argparser.arg_name(param.name) + kwargs['metavar'] = argparser.arg_name(param.name) if param.kind == inspect.Parameter.VAR_POSITIONAL: kwargs['nargs'] = '*' if self._star_args_optional else '+' @@ -320,9 +319,8 @@ class Command: self.opt_args[param.name] = long_flag, short_flag if not is_bool: self.flags_with_args += [short_flag, long_flag] - else: - if not arg_info.hide: - self.pos_args.append((param.name, name)) + elif not arg_info.hide: + self.pos_args.append((param.name, name)) return args def _get_type(self, param): diff --git a/qutebrowser/commands/parser.py b/qutebrowser/commands/parser.py index 06a20cdf6..5ef46f5e5 100644 --- a/qutebrowser/commands/parser.py +++ b/qutebrowser/commands/parser.py @@ -43,10 +43,18 @@ class CommandParser: Attributes: _partial_match: Whether to allow partial command matches. + _find_similar: Whether to find similar matches on unknown commands. + If we use this for completion, errors are not shown in the UI, + so we don't need to search. """ - def __init__(self, partial_match: bool = False) -> None: + def __init__( + self, + partial_match: bool = False, + find_similar: bool = False, + ) -> None: self._partial_match = partial_match + self._find_similar = find_similar def _get_alias(self, text: str, *, default: str) -> str: """Get an alias from the config. @@ -95,7 +103,7 @@ class CommandParser: """ text = text.strip().lstrip(':').strip() if not text: - raise cmdexc.NoSuchCommandError("No command given") + raise cmdexc.EmptyCommandError if aliases: text = self._get_alias(text, default=text) @@ -128,7 +136,7 @@ class CommandParser: cmdstr, sep, argstr = text.partition(' ') if not cmdstr: - raise cmdexc.NoSuchCommandError("No command given") + raise cmdexc.EmptyCommandError if self._partial_match: cmdstr = self._completion_match(cmdstr) @@ -136,7 +144,10 @@ class CommandParser: try: cmd = objects.commands[cmdstr] except KeyError: - raise cmdexc.NoSuchCommandError(f'{cmdstr}: no such command') + raise cmdexc.NoSuchCommandError.for_cmd( + cmdstr, + all_commands=list(objects.commands) if self._find_similar else [], + ) args = self._split_args(cmd, argstr, keep) if keep and args: diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 5fb054455..e3cd0cc97 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -138,9 +138,12 @@ class CommandRunner(AbstractCommandRunner): _win_id: The window this CommandRunner is associated with. """ - def __init__(self, win_id, partial_match=False, parent=None): + def __init__(self, win_id, partial_match=False, find_similar=True, parent=None): super().__init__(parent) - self._parser = parser.CommandParser(partial_match=partial_match) + self._parser = parser.CommandParser( + partial_match=partial_match, + find_similar=find_similar, + ) self._win_id = win_id @contextlib.contextmanager diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index 70c639207..c776324ac 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -63,8 +63,7 @@ class _QtFIFOReader(QObject): self._fifo = os.fdopen(fd, 'r') self._notifier = QSocketNotifier(cast(sip.voidptr, fd), QSocketNotifier.Read, self) - self._notifier.activated.connect( # type: ignore[attr-defined] - self.read_line) + self._notifier.activated.connect(self.read_line) @pyqtSlot() def read_line(self): @@ -342,9 +341,8 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner): self._kwargs = kwargs try: - handle = tempfile.NamedTemporaryFile(delete=False) - handle.close() - self._filepath = handle.name + with tempfile.NamedTemporaryFile(delete=False) as handle: + self._filepath = handle.name except OSError as e: message.error("Error while creating tempfile: {}".format(e)) return diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 778333854..cf6984288 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -29,6 +29,7 @@ from qutebrowser.commands import parser, cmdexc from qutebrowser.misc import objects, split from qutebrowser.utils import log, utils, debug, objreg from qutebrowser.completion.models import miscmodels +from qutebrowser.completion import completionwidget if TYPE_CHECKING: from qutebrowser.browser import browsertab @@ -39,7 +40,8 @@ class CompletionInfo: """Context passed into all completion functions.""" config: config.Config - keyconf: config.KeyConfig + # pylint: disable-next=used-before-assignment + keyconf: config.KeyConfig # type: ignore[name-defined] win_id: int cur_tab: 'browsertab.AbstractTab' @@ -74,10 +76,15 @@ class Completer(QObject): def __repr__(self): return utils.get_repr(self) + def _completion(self) -> completionwidget.CompletionView: + """Convenience method to get the current completion.""" + completion = self.parent() + assert isinstance(completion, completionwidget.CompletionView), completion + return completion + def _model(self): """Convenience method to get the current completion model.""" - completion = self.parent() - return completion.model() + return self._completion().model() def _get_new_completion(self, before_cursor, under_cursor): """Get the completion function based on the current command text. @@ -158,6 +165,7 @@ class Completer(QObject): # cursor is in a space between two existing words parts.insert(i, '') prefix = [x.strip() for x in parts[:i]] + # pylint: disable-next=unnecessary-list-index-lookup center = parts[i].strip() # strip trailing whitespace included as a separate token postfix = [x.strip() for x in parts[i+1:] if not x.isspace()] @@ -230,7 +238,7 @@ class Completer(QObject): @pyqtSlot() def _update_completion(self): """Check if completions are available and activate them.""" - completion = self.parent() + completion = self._completion() if self._cmd.prefix() != ':': # This is a search or gibberish, so we don't need to complete diff --git a/qutebrowser/completion/completiondelegate.py b/qutebrowser/completion/completiondelegate.py index 9ea82e876..ae02426a5 100644 --- a/qutebrowser/completion/completiondelegate.py +++ b/qutebrowser/completion/completiondelegate.py @@ -33,6 +33,7 @@ from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption, from qutebrowser.config import config from qutebrowser.utils import qtutils +from qutebrowser.completion import completionwidget class _Highlighter(QSyntaxHighlighter): @@ -233,6 +234,7 @@ class CompletionItemDelegate(QStyledItemDelegate): if index.parent().isValid(): view = self.parent() + assert isinstance(view, completionwidget.CompletionView), view pattern = view.pattern columns_to_filter = index.model().columns_to_filter(index) if index.column() in columns_to_filter and pattern: @@ -299,7 +301,7 @@ class CompletionItemDelegate(QStyledItemDelegate): size = self._style.sizeFromContents(QStyle.CT_ItemViewItem, self._opt, docsize, self._opt.widget) qtutils.ensure_valid(size) - return size + QSize(10, 3) # type: ignore[operator] + return size + QSize(10, 3) def paint(self, painter, option, index): """Override the QStyledItemDelegate paint function. diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index dd8e267fe..de9100a2e 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -30,6 +30,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize from qutebrowser.config import config, stylesheet from qutebrowser.completion import completiondelegate +from qutebrowser.completion.models import completionmodel from qutebrowser.utils import utils, usertypes, debug, log, qtutils from qutebrowser.api import cmdutils if TYPE_CHECKING: @@ -148,6 +149,15 @@ class CompletionView(QTreeView): def __repr__(self): return utils.get_repr(self) + def _model(self) -> completionmodel.CompletionModel: + """Get the current completion model. + + Ensures the model is not None. + """ + model = self.model() + assert isinstance(model, completionmodel.CompletionModel), model + return model + @pyqtSlot(str) def _on_config_changed(self, option): if option in ['completion.height', 'completion.shrink']: @@ -158,7 +168,7 @@ class CompletionView(QTreeView): if self.model() is None: return width = self.size().width() - column_widths = self.model().column_widths + column_widths = self._model().column_widths pixel_widths = [(width * perc // 100) for perc in column_widths] delta = self.verticalScrollBar().sizeHint().width() @@ -182,21 +192,22 @@ class CompletionView(QTreeView): Return: A QModelIndex. """ + model = self._model() idx = self.selectionModel().currentIndex() if not idx.isValid(): # No item selected yet if upwards: - return self.model().last_item() + return model.last_item() else: - return self.model().first_item() + return model.first_item() while True: idx = self.indexAbove(idx) if upwards else self.indexBelow(idx) # wrap around if we arrived at beginning/end if not idx.isValid() and upwards: - return self.model().last_item() + return model.last_item() elif not idx.isValid() and not upwards: - idx = self.model().first_item() + idx = model.first_item() self.scrollTo(idx.parent()) return idx elif idx.parent().isValid(): @@ -216,7 +227,7 @@ class CompletionView(QTreeView): """ old_idx = self.selectionModel().currentIndex() idx = old_idx - model = self.model() + model = self._model() if not idx.isValid(): # No item selected yet @@ -259,6 +270,7 @@ class CompletionView(QTreeView): A QModelIndex. """ idx = self.selectionModel().currentIndex() + model = self._model() if not idx.isValid(): return self._next_idx(upwards).sibling(0, 0) idx = idx.parent() @@ -267,10 +279,10 @@ class CompletionView(QTreeView): idx = idx.sibling(idx.row() + direction, 0) if not idx.isValid() and upwards: # wrap around to the first item of the last category - return self.model().last_item().sibling(0, 0) + return model.last_item().sibling(0, 0) elif not idx.isValid() and not upwards: # wrap around to the first item of the first category - idx = self.model().first_item() + idx = model.first_item() self.scrollTo(idx.parent()) return idx elif idx.isValid() and idx.child(0, 0).isValid(): @@ -327,7 +339,7 @@ class CompletionView(QTreeView): selmodel.setCurrentIndex( idx, - QItemSelectionModel.ClearAndSelect | # type: ignore[arg-type] + QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) # if the last item is focused, try to fetch more @@ -335,7 +347,7 @@ class CompletionView(QTreeView): if not self.visualRect(next_idx).isValid(): self.expandAll() - count = self.model().count() + count = self._model().count() if count == 0: self.hide() elif count == 1 and config.val.completion.quick: @@ -382,14 +394,14 @@ class CompletionView(QTreeView): return self.pattern = pattern with debug.log_time(log.completion, 'Set pattern {}'.format(pattern)): - self.model().set_pattern(pattern) + self._model().set_pattern(pattern) self.selectionModel().clear() self._maybe_update_geometry() self._maybe_show() def _maybe_show(self): if (config.val.completion.show == 'always' and - self.model().count() > 0): + self._model().count() > 0): self.show() else: self.hide() @@ -435,7 +447,7 @@ class CompletionView(QTreeView): indexes = selected.indexes() if not indexes: return - data = str(self.model().data(indexes[0])) + data = str(self._model().data(indexes[0])) self.selection_changed.emit(data) def resizeEvent(self, e): @@ -458,7 +470,7 @@ class CompletionView(QTreeView): index = self.currentIndex() if not index.isValid(): raise cmdutils.CommandError("No item selected!") - self.model().delete_cur_item(index) + self._model().delete_cur_item(index) @cmdutils.register(instance='completion', modes=[usertypes.KeyMode.command], scope='window') @@ -473,7 +485,7 @@ class CompletionView(QTreeView): index = self.currentIndex() if not index.isValid(): raise cmdutils.CommandError("No item selected!") - text = self.model().data(index) + text = self._model().data(index) if not utils.supports_selection(): sel = False diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py index 81f4bba8e..4b8fee299 100644 --- a/qutebrowser/completion/models/completionmodel.py +++ b/qutebrowser/completion/models/completionmodel.py @@ -70,6 +70,7 @@ class CompletionModel(QAbstractItemModel): Args: index: The QModelIndex to get item flags for. + role: The Qt ItemRole to get the data for. Return: The item data, or None on an invalid index. """ @@ -179,10 +180,11 @@ class CompletionModel(QAbstractItemModel): pattern: The filter pattern to set. """ log.completion.debug("Setting completion pattern '{}'".format(pattern)) - self.layoutAboutToBeChanged.emit() # type: ignore[attr-defined] + self.layoutAboutToBeChanged.emit() for cat in self._categories: - cat.set_pattern(pattern) - self.layoutChanged.emit() # type: ignore[attr-defined] + # FIXME:mypy define a Protocol for set_pattern? + cat.set_pattern(pattern) # type: ignore[attr-defined] + self.layoutChanged.emit() def first_item(self): """Return the index of the first child (non-category) in the model.""" diff --git a/qutebrowser/completion/models/configmodel.py b/qutebrowser/completion/models/configmodel.py index 736d09644..6c85fbb29 100644 --- a/qutebrowser/completion/models/configmodel.py +++ b/qutebrowser/completion/models/configmodel.py @@ -44,16 +44,22 @@ def customized_option(*, info): def list_option(*, info): """A CompletionModel filled with settings whose values are lists.""" - predicate = lambda opt: (isinstance(info.config.get_obj(opt.name), - list) and not opt.no_autoconfig) - return _option(info, "List options", predicate) + return _option( + info, + "List options", + lambda opt: (isinstance(info.config.get_obj(opt.name), list) and + not opt.no_autoconfig) + ) def dict_option(*, info): """A CompletionModel filled with settings whose values are dicts.""" - predicate = lambda opt: (isinstance(info.config.get_obj(opt.name), - dict) and not opt.no_autoconfig) - return _option(info, "Dict options", predicate) + return _option( + info, + "Dict options", + lambda opt: (isinstance(info.config.get_obj(opt.name), dict) and + not opt.no_autoconfig) + ) def _option(info, title, predicate): @@ -78,7 +84,7 @@ def value(optname, *values, info): Args: optname: The name of the config option this model shows. - values: The values already provided on the command line. + *values: The values already provided on the command line. info: A CompletionInfo instance. """ model = completionmodel.CompletionModel(column_widths=(30, 70, 0)) @@ -137,6 +143,7 @@ def bind(key, *, info): Args: key: the key being bound. + info: A CompletionInfo instance. """ model = completionmodel.CompletionModel(column_widths=(20, 60, 20)) data = _bind_current_default(key, info) diff --git a/qutebrowser/completion/models/filepathcategory.py b/qutebrowser/completion/models/filepathcategory.py index b7d74f57a..fd2cb58ce 100644 --- a/qutebrowser/completion/models/filepathcategory.py +++ b/qutebrowser/completion/models/filepathcategory.py @@ -65,7 +65,7 @@ class FilePathCategory(QAbstractListModel): try: return glob.glob(glob.escape(val) + '*') except ValueError as e: # pragma: no cover - # e.g. "embedded null byte" with \x00 on Python 3.6 and 3.7 + # e.g. "embedded null byte" with \x00 on Python 3.7 log.completion.debug(f"Failed to glob: {e}") return [] diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 7642bf904..77072c720 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -103,17 +103,23 @@ def session(*, info=None): return model -def _tabs(*, win_id_filter=lambda _win_id: True, add_win_id=True): +def _tabs(*, win_id_filter=lambda _win_id: True, add_win_id=True, cur_win_id=None): """Helper to get the completion model for tabs/other_tabs. Args: win_id_filter: A filter function for window IDs to include. Should return True for all included windows. add_win_id: Whether to add the window ID to the completion items. + cur_win_id: Window ID to be passed from info.win_id """ def delete_tab(data): """Close the selected tab.""" - win_id, tab_index = data[0].split('/') + if cur_win_id is None: + win_id, tab_index = data[0].split('/') + else: + win_id = cur_win_id + tab_index = data[0] + tabbed_browser = objreg.get('tabbed-browser', scope='window', window=int(win_id)) tabbed_browser.on_tab_close_requested(int(tab_index) - 1) @@ -177,13 +183,15 @@ def other_tabs(*, info): Used for the tab-take command. """ - return _tabs(win_id_filter=lambda win_id: win_id != info.win_id) + return _tabs( + win_id_filter=lambda win_id: win_id != info.win_id, + cur_win_id=info.win_id) def tab_focus(*, info): """A model to complete on open tabs in the current window.""" model = _tabs(win_id_filter=lambda win_id: win_id == info.win_id, - add_win_id=False) + add_win_id=False, cur_win_id=info.win_id) special = [ ("last", "Focus the last-focused tab"), @@ -230,9 +238,7 @@ def _qdatetime_to_completion_format(qdate): if not qdate.isValid(): ts = 0 else: - ts = qdate.toMSecsSinceEpoch() - if ts < 0: - ts = 0 + ts = max(qdate.toMSecsSinceEpoch(), 0) pydate = datetime.datetime.fromtimestamp(ts / 1000) return pydate.strftime(config.val.completion.timestamp_format) diff --git a/qutebrowser/components/braveadblock.py b/qutebrowser/components/braveadblock.py index 21319cb1b..977aa3ef2 100644 --- a/qutebrowser/components/braveadblock.py +++ b/qutebrowser/components/braveadblock.py @@ -175,11 +175,12 @@ class BraveAdBlocker: hasattr(adblock, "__file__")): proc = subprocess.run( ['pacman', '-Qo', adblock.__file__], - stdout=subprocess.PIPE, - universal_newlines=True, + capture_output=True, + text=True, check=False, ) logger.debug(proc.stdout) + logger.debug(proc.stderr) raise def _is_blocked( @@ -266,14 +267,13 @@ class BraveAdBlocker: except DeserializationError: message.error("Reading adblock filter data failed (corrupted data?). " "Please run :adblock-update.") - else: - if ( - config.val.content.blocking.adblock.lists - and not self._has_basedir - and config.val.content.blocking.enabled - and self.enabled - ): - message.info("Run :adblock-update to get adblock lists.") + elif ( + config.val.content.blocking.adblock.lists + and not self._has_basedir + and config.val.content.blocking.enabled + and self.enabled + ): + message.info("Run :adblock-update to get adblock lists.") def adblock_update(self) -> blockutils.BlocklistDownloads: """Update the adblock block lists.""" diff --git a/qutebrowser/components/hostblock.py b/qutebrowser/components/hostblock.py index 2d6086245..191719f10 100644 --- a/qutebrowser/components/hostblock.py +++ b/qutebrowser/components/hostblock.py @@ -64,9 +64,10 @@ def get_fileobj(byte_io: IO[bytes]) -> IO[bytes]: byte_io.seek(0) # rewind downloaded file if zipfile.is_zipfile(byte_io): byte_io.seek(0) # rewind what zipfile.is_zipfile did - zf = zipfile.ZipFile(byte_io) - filename = _guess_zip_filename(zf) - byte_io = zf.open(filename, mode="r") + with zipfile.ZipFile(byte_io) as zf: + filename = _guess_zip_filename(zf) + # pylint: disable=consider-using-with + byte_io = zf.open(filename, mode="r") else: byte_io.seek(0) # rewind what zipfile.is_zipfile did return byte_io @@ -132,10 +133,16 @@ class HostBlocker: host = request_url.host() - return any( - hostname in self._blocked_hosts or hostname in self._config_blocked_hosts - for hostname in urlutils.widened_hostnames(host) - ) + if config.get("content.blocking.hosts.block_subdomains"): + return any( + hostname in self._blocked_hosts + or hostname in self._config_blocked_hosts + for hostname in urlutils.widened_hostnames(host) + ) + else: + return ( + host in self._blocked_hosts or host in self._config_blocked_hosts + ) def filter_request(self, info: interceptor.Request) -> None: """Block the given request if necessary.""" diff --git a/qutebrowser/components/misccommands.py b/qutebrowser/components/misccommands.py index 120806bfe..53c057e26 100644 --- a/qutebrowser/components/misccommands.py +++ b/qutebrowser/components/misccommands.py @@ -17,6 +17,9 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. +# To allow count being documented +# pylint: disable=differing-param-doc + """Various commands.""" import os @@ -24,7 +27,7 @@ import signal import functools import logging import pathlib -from typing import Optional +from typing import Optional, List, Callable, cast try: import hunter @@ -80,7 +83,7 @@ def _print_preview(tab: apitypes.Tab) -> None: diag = QPrintPreviewDialog(tab) diag.setAttribute(Qt.WA_DeleteOnClose) diag.setWindowFlags( - diag.windowFlags() | # type: ignore[operator, arg-type] + diag.windowFlags() | Qt.WindowMaximizeButtonHint | Qt.WindowMinimizeButtonHint) diag.paintRequested.connect(functools.partial( @@ -183,7 +186,10 @@ def screenshot( raise cmdutils.CommandError( f"File {filename} already exists (use --force to overwrite)") - qrect = None if rect is None else utils.parse_rect(rect) + try: + qrect = None if rect is None else utils.parse_rect(rect) + except ValueError as e: + raise cmdutils.CommandError(str(e)) pic = tab.grab_pixmap(qrect) if pic is None: @@ -217,13 +223,33 @@ def insert_text(tab: apitypes.Tab, text: str) -> None: tab.elements.find_focused(_insert_text_cb) +def _wrap_find_at_pos(value: str, tab: apitypes.Tab, + callback: Callable[[Optional[apitypes.WebElement]], None] + ) -> None: + try: + point = utils.parse_point(value) + except ValueError as e: + message.error(str(e)) + return + tab.elements.find_at_pos(point, callback) + + +_FILTER_ERRORS = { + 'id': lambda x: f'with ID "{x}"', + 'css': lambda x: f'matching CSS selector "{x}"', + 'focused': lambda _: 'with focus', + 'position': lambda x: 'at position {x}', +} + + @cmdutils.register() @cmdutils.argument('tab', value=cmdutils.Value.cur_tab) -@cmdutils.argument('filter_', choices=['id']) -def click_element(tab: apitypes.Tab, filter_: str, value: str, *, +@cmdutils.argument('filter_', choices=['id', 'css', 'position', 'focused']) +def click_element(tab: apitypes.Tab, filter_: str, value: str = None, *, target: apitypes.ClickTarget = apitypes.ClickTarget.normal, - force_event: bool = False) -> None: + force_event: bool = False, + select_first: bool = False) -> None: """Click the element matching the given filter. The given filter needs to result in exactly one element, otherwise, an @@ -231,27 +257,55 @@ def click_element(tab: apitypes.Tab, filter_: str, value: str, *, Args: filter_: How to filter the elements. - id: Get an element based on its ID. - value: The value to filter for. + + - id: Get an element based on its ID. + - css: Filter by a CSS selector. + - position: Click the element at specified position. + Specify `value` as 'x,y'. + - focused: Click the currently focused element. + value: The value to filter for. Optional for 'focused' filter. target: How to open the clicked element (normal/tab/tab-bg/window). force_event: Force generating a fake click event. + select_first: Select first matching element if there are multiple. """ - def single_cb(elem: Optional[apitypes.WebElement]) -> None: - """Click a single element.""" - if elem is None: - message.error("No element found with id {}!".format(value)) - return + def do_click(elem: apitypes.WebElement) -> None: try: elem.click(target, force_event=force_event) except apitypes.WebElemError as e: message.error(str(e)) + + def single_cb(elem: Optional[apitypes.WebElement]) -> None: + """Click a single element.""" + if elem is None: + message.error(f"No element found {_FILTER_ERRORS[filter_](value)}!") + return + + do_click(elem) + + def multiple_cb(elems: List[apitypes.WebElement]) -> None: + if not elems: + message.error(f"No element found {_FILTER_ERRORS[filter_](value)}!") + return + + if not select_first and len(elems) > 1: + message.error(f"Multiple elements found {_FILTER_ERRORS[filter_](value)}!") return + do_click(elems[0]) + + if value is None and filter_ != 'focused': + raise cmdutils.CommandError("Argument 'value' is only " + "optional with filter 'focused'!") + handlers = { - 'id': (tab.elements.find_id, single_cb), + 'id': lambda: tab.elements.find_id(elem_id=value, callback=single_cb), + 'css': lambda: + tab.elements.find_css(value, callback=multiple_cb, error_cb=message.error), + 'position': lambda: + _wrap_find_at_pos(cast(str, value), tab=tab, callback=single_cb), + 'focused': lambda: tab.elements.find_focused(callback=single_cb), } - handler, callback = handlers[filter_] - handler(value, callback) + handlers[filter_]() @cmdutils.register(debug=True) diff --git a/qutebrowser/components/readlinecommands.py b/qutebrowser/components/readlinecommands.py index da9c515dd..37fa92127 100644 --- a/qutebrowser/components/readlinecommands.py +++ b/qutebrowser/components/readlinecommands.py @@ -19,7 +19,8 @@ """Bridge to provide readline-like shortcuts for QLineEdits.""" -from typing import Iterable, Optional, MutableMapping +import os +from typing import Iterable, Optional, MutableMapping, Any, Callable from PyQt5.QtWidgets import QApplication, QLineEdit @@ -41,7 +42,7 @@ class _ReadlineBridge: """Get the currently active QLineEdit.""" # FIXME add this to api.utils or so qapp = QApplication.instance() - assert qapp is not None + assert isinstance(qapp, QApplication), qapp w = qapp.focusWidget() if isinstance(w, QLineEdit): @@ -90,8 +91,13 @@ class _ReadlineBridge: def kill_line(self) -> None: self._dispatch('end', mark=True, delete=True) - def _rubout(self, delim: Iterable[str]) -> None: - """Delete backwards using the characters in delim as boundaries.""" + def rubout(self, delim: Iterable[str]) -> None: + """Delete backwards using the characters in delim as boundaries. + + With delim=[' '], this acts like unix-word-rubout. + With delim=[' ', '/'], this acts like unix-filename-rubout. + With delim=[os.sep], this serves as a more useful filename-rubout. + """ widget = self._widget() if widget is None: return @@ -100,27 +106,65 @@ class _ReadlineBridge: target_position = cursor_position + # First scan any trailing boundaries, e.g.: + # /some/path//| -> /some/path[//] + # 0 ^ 12 0 ^ 9 + # (cursor) (target) is_boundary = True while is_boundary and target_position > 0: is_boundary = text[target_position - 1] in delim target_position -= 1 + # Then scan anything not a boundary, e.g. + # /some/path -> /some/[path//] + # 0 ^ 9 0 ^ 5 + # (old target) (target) is_boundary = False while not is_boundary and target_position > 0: is_boundary = text[target_position - 1] in delim target_position -= 1 + # Account for the last remaining character. + # With e.g.: + # + # somepath| + # 0 8 + # + # We exit the loop above with cursor_position=8 and target_position=0. + # However, we want to *keep* the found boundary usually, thus only + # trying to delete 7 chars: + # + # s[omepath] + # + # However, that would be wrong: We also want to remove the *initial* + # character, if it was not a boundary. + # We can't say "target_position >= 0" above, because that'd falsely + # check whether text[-1] was a boundary. + if not is_boundary: + # target_position can never be negative, and if it's > 0, then the + # loop above could only have exited because of is_boundary=True, + # thus we can only end up here if target_position=0. + assert target_position == 0, (text, delim) + target_position -= 1 + + # Finally, move back as calculated - in the example above: + # + # vvvvvv---- 12 - 5 - 1 = 6 chars to delete. + # /some/[path//]| + # ^ 5 ^ 12 + # (target) (cursor) + # + # If we have a text without an initial boundary: + # + # vvvvvvvv---- 8 - (-1) - 1 = 8 chars to delete. + # [somepath]| + # ^ -1 ^ 8 + # (target) (cursor) moveby = cursor_position - target_position - 1 widget.cursorBackward(True, moveby) self._deleted[widget] = widget.selectedText() widget.del_() - def unix_word_rubout(self) -> None: - self._rubout([' ']) - - def unix_filename_rubout(self) -> None: - self._rubout([' ', '/']) - def backward_kill_word(self) -> None: self._dispatch('cursorWordBackward', mark=True, delete=True) @@ -142,11 +186,15 @@ class _ReadlineBridge: bridge = _ReadlineBridge() -_register = cmdutils.register( - modes=[cmdutils.KeyMode.command, cmdutils.KeyMode.prompt]) -@_register +def _register(**kwargs: Any) -> Callable[..., Any]: + return cmdutils.register( + modes=[cmdutils.KeyMode.command, cmdutils.KeyMode.prompt], + **kwargs) + + +@_register() def rl_backward_char() -> None: """Move back a character. @@ -155,7 +203,7 @@ def rl_backward_char() -> None: bridge.backward_char() -@_register +@_register() def rl_forward_char() -> None: """Move forward a character. @@ -164,7 +212,7 @@ def rl_forward_char() -> None: bridge.forward_char() -@_register +@_register() def rl_backward_word() -> None: """Move back to the start of the current or previous word. @@ -173,7 +221,7 @@ def rl_backward_word() -> None: bridge.backward_word() -@_register +@_register() def rl_forward_word() -> None: """Move forward to the end of the next word. @@ -182,7 +230,7 @@ def rl_forward_word() -> None: bridge.forward_word() -@_register +@_register() def rl_beginning_of_line() -> None: """Move to the start of the line. @@ -191,7 +239,7 @@ def rl_beginning_of_line() -> None: bridge.beginning_of_line() -@_register +@_register() def rl_end_of_line() -> None: """Move to the end of the line. @@ -200,7 +248,7 @@ def rl_end_of_line() -> None: bridge.end_of_line() -@_register +@_register() def rl_unix_line_discard() -> None: """Remove chars backward from the cursor to the beginning of the line. @@ -209,7 +257,7 @@ def rl_unix_line_discard() -> None: bridge.unix_line_discard() -@_register +@_register() def rl_kill_line() -> None: """Remove chars from the cursor to the end of the line. @@ -218,26 +266,57 @@ def rl_kill_line() -> None: bridge.kill_line() -@_register +@_register(deprecated="Use :rl-rubout ' ' instead.") def rl_unix_word_rubout() -> None: """Remove chars from the cursor to the beginning of the word. This acts like readline's unix-word-rubout. Whitespace is used as a word delimiter. """ - bridge.unix_word_rubout() + bridge.rubout([" "]) -@_register +@_register( + deprecated='Use :rl-filename-rubout or :rl-rubout " /" instead ' + '(see their `:help` for details).' +) def rl_unix_filename_rubout() -> None: """Remove chars from the cursor to the previous path separator. This acts like readline's unix-filename-rubout. """ - bridge.unix_filename_rubout() + bridge.rubout([" ", "/"]) + + +@_register() +def rl_rubout(delim: str) -> None: + r"""Delete backwards using the given characters as boundaries. + + With " ", this acts like readline's `unix-word-rubout`. + + With " /", this acts like readline's `unix-filename-rubout`, but consider + using `:rl-filename-rubout` instead: It uses the OS path separator (i.e. `\` + on Windows) and ignores spaces. + + Args: + delim: A string of characters (or a single character) until which text + will be deleted. + """ + bridge.rubout(list(delim)) + + +@_register() +def rl_filename_rubout() -> None: + r"""Delete backwards using the OS path separator as boundary. + + For behavior that matches readline's `unix-filename-rubout` exactly, use + `:rl-rubout "/ "` instead. This command uses the OS path separator (i.e. + `\` on Windows) and ignores spaces. + """ + bridge.rubout(os.sep) -@_register +@_register() def rl_backward_kill_word() -> None: """Remove chars from the cursor to the beginning of the word. @@ -247,7 +326,7 @@ def rl_backward_kill_word() -> None: bridge.backward_kill_word() -@_register +@_register() def rl_kill_word() -> None: """Remove chars from the cursor to the end of the current word. @@ -256,7 +335,7 @@ def rl_kill_word() -> None: bridge.kill_word() -@_register +@_register() def rl_yank() -> None: """Paste the most recently deleted text. @@ -265,7 +344,7 @@ def rl_yank() -> None: bridge.yank() -@_register +@_register() def rl_delete_char() -> None: """Delete the character after the cursor. @@ -274,7 +353,7 @@ def rl_delete_char() -> None: bridge.delete_char() -@_register +@_register() def rl_backward_delete_char() -> None: """Delete the character before the cursor. diff --git a/qutebrowser/components/scrollcommands.py b/qutebrowser/components/scrollcommands.py index 757c23b2b..2287041d7 100644 --- a/qutebrowser/components/scrollcommands.py +++ b/qutebrowser/components/scrollcommands.py @@ -19,6 +19,7 @@ """Scrolling-related commands.""" +from typing import Dict, Callable from qutebrowser.api import cmdutils, apitypes @@ -54,7 +55,8 @@ def scroll(tab: apitypes.Tab, direction: str, count: int = 1) -> None: (up/down/left/right/top/bottom). count: multiplier """ - funcs = { + # FIXME:mypy Use a callback protocol to enforce having 'count'? + funcs: Dict[str, Callable[..., None]] = { 'up': tab.scroller.up, 'down': tab.scroller.down, 'left': tab.scroller.left, diff --git a/qutebrowser/components/utils/blockutils.py b/qutebrowser/components/utils/blockutils.py index bd27baece..98681a488 100644 --- a/qutebrowser/components/utils/blockutils.py +++ b/qutebrowser/components/utils/blockutils.py @@ -125,7 +125,7 @@ class BlocklistDownloads(QObject): filename: path to a local file to import. """ try: - fileobj = open(filename, "rb") + fileobj = open(filename, "rb") # pylint: disable=consider-using-with except OSError as e: message.error( "blockutils: Error while reading {}: {}".format(filename, e.strerror) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 437a54a33..918c9b894 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -373,7 +373,11 @@ class Config(QObject): deleted = name in configdata.MIGRATIONS.deleted renamed = configdata.MIGRATIONS.renamed.get(name) exception = configexc.NoOptionError( - name, deleted=deleted, renamed=renamed) + name, + deleted=deleted, + renamed=renamed, + all_names=list(configdata.DATA), + ) raise exception from None def ensure_has_opt(self, name: str) -> None: @@ -387,6 +391,8 @@ class Config(QObject): """Get the given setting converted for Python code. Args: + name: The name of the setting to get. + url: The URL to get the value for. fallback: Use the global value if there's no URL-specific one. """ opt = self.get_opt(name) diff --git a/qutebrowser/config/configcommands.py b/qutebrowser/config/configcommands.py index 143b02fca..407c74214 100644 --- a/qutebrowser/config/configcommands.py +++ b/qutebrowser/config/configcommands.py @@ -101,7 +101,7 @@ class ConfigCommands: Args: option: The name of the option. value: The value to set. - pattern: The URL pattern to use. + pattern: The link:configuring{outfilesuffix}#patterns[URL pattern] to use. temp: Set value temporarily until qutebrowser is closed. print_: Print the value after setting. """ @@ -151,9 +151,12 @@ class ConfigCommands: default: If given, restore a default binding. """ if key is None: + url = QUrl('qute://bindings') + if mode != "normal": + url.setFragment(mode) tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) - tabbed_browser.load_url(QUrl('qute://bindings'), newtab=True) + tabbed_browser.load_url(url, newtab=True) return seq = self._parse_key(key) @@ -204,8 +207,8 @@ class ConfigCommands: Args: option: The name of the option. - values: The values to cycle through. - pattern: The URL pattern to use. + *values: The values to cycle through. + pattern: The link:configuring{outfilesuffix}#patterns[URL pattern] to use. temp: Set value temporarily until qutebrowser is closed. print_: Print the value after setting. """ @@ -259,7 +262,7 @@ class ConfigCommands: Args: option: The name of the option. - pattern: The URL pattern to use. + pattern: The link:configuring{outfilesuffix}#patterns[URL pattern] to use. temp: Set value temporarily until qutebrowser is closed. """ parsed_pattern = self._parse_pattern(pattern) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index a24da4e82..7b79e761d 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -239,6 +239,9 @@ qt.force_platformtheme: based on the desktop environment. qt.process_model: + renamed: qt.chromium.process_model + +qt.chromium.process_model: type: name: String valid_values: @@ -268,6 +271,9 @@ qt.process_model: - https://doc.qt.io/qt-5/qtwebengine-features.html#process-models qt.low_end_device_mode: + renamed: qt.chromium.low_end_device_mode + +qt.chromium.low_end_device_mode: type: name: String valid_values: @@ -284,6 +290,39 @@ qt.low_end_device_mode: This improves the RAM usage of renderer processes, at the expense of performance. +qt.chromium.sandboxing: + type: + name: String + valid_values: + - enable-all: Enable all available sandboxing mechanisms. + - disable-seccomp-bpf: Disable the Seccomp BPF filter sandbox (Linux only). + - disable-all: Disable all sandboxing (**not recommended!**). + default: enable-all + backend: QtWebEngine + restart: true + no_autoconfig: true # due to it being dangerous + # yamllint disable rule:line-length + desc: >- + What sandboxing mechanisms in Chromium to use. + + Chromium has various sandboxing layers, which should be enabled for normal + browser usage. Mainly for testing and development, it's possible to disable + individual sandboxing layers via this setting. + + Open `chrome://sandbox` to see the current sandbox status. + + Changing this setting is only recommended if you know what you're doing, as + it **disables one of Chromium's security layers**. To avoid sandboxing being + accidentally disabled persistently, this setting can only be set via + `config.py`, not via `:set`. + + See the Chromium documentation for more details: + + - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/linux/sandboxing.md[Linux] + - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox.md[Windows] + - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox_faq.md[FAQ (Windows-centric)] + # yamllint enable rule:line-length + qt.highdpi: type: Bool default: false @@ -616,6 +655,7 @@ content.headers.custom: valtype: name: String encoding: ascii + none_ok: true none_ok: true supports_pattern: true desc: Custom headers for qutebrowser HTTP requests. @@ -649,7 +689,9 @@ content.headers.referer: When to send the Referer header. The Referer header tells websites from which website you were coming from - when visiting them. + when visiting them. Note that with QtWebEngine, websites can override this + preference by setting the `Referrer-Policy:` header, so that any websites + visited from them get the full referer. No restart is needed with QtWebKit. @@ -677,14 +719,14 @@ content.headers.user_agent: # Vim-protip: Place your cursor below this comment and run # :r!python scripts/dev/ua_fetch.py - - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, - like Gecko) Chrome/90.0.4430.93 Safari/537.36" - - Chrome 90 Win10 + like Gecko) Chrome/97.0.4692.99 Safari/537.36" + - Chrome 97 Win10 - - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 - (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36" - - Chrome 90 macOS + (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36" + - Chrome 97 macOS - - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like - Gecko) Chrome/90.0.4430.93 Safari/537.36" - - Chrome 90 Linux + Gecko) Chrome/97.0.4692.99 Safari/537.36" + - Chrome 97 Linux supports_pattern: true desc: | User agent to send. @@ -747,6 +789,15 @@ content.blocking.hosts.lists: The file `~/.config/qutebrowser/blocked-hosts` is always read if it exists. +content.blocking.hosts.block_subdomains: + default: true + type: Bool + desc: >- + Block subdomains of blocked hosts. + + Note: If only a single subdomain is blocked but should be allowed, consider + using `content.blocking.whitelist` instead. + content.blocking.method: default: auto type: @@ -818,9 +869,14 @@ content.javascript.alert: type: Bool desc: Show javascript alerts. -content.javascript.can_access_clipboard: - default: false - type: Bool +content.javascript.clipboard: + default: none + type: + name: String + valid_values: + - none: Disable access to clipboard. + - access: Allow reading from and writing to the clipboard. + - access-paste: Allow accessing the clipboard and pasting clipboard content. supports_pattern: true desc: >- Allow JavaScript to read from or write to the clipboard. @@ -1006,10 +1062,12 @@ content.pdfjs: type: Bool supports_pattern: true desc: >- - Allow pdf.js to view PDF files in the browser. + Display PDF files via PDF.js in the browser without showing a download prompt. Note that the files can still be downloaded by clicking the download button - in the pdf.js viewer. + in the pdf.js viewer. With this set to `false`, the `:prompt-open-download + --pdfjs` command (bound to `<Ctrl-p>` by default) can be used in the + download prompt. content.persistent_storage: default: ask @@ -1049,7 +1107,8 @@ content.proxy: `http://...` URL. Note that with QtWebEngine, it will take a couple of seconds until the - change is applied, if this value is changed at runtime. + change is applied, if this value is changed at runtime. Authentication for + SOCKS proxies isn't supported due to Chromium limitations. content.proxy_dns_requests: default: true @@ -1344,6 +1403,18 @@ downloads.position: default: top desc: Where to show the downloaded files. +downloads.prevent_mixed_content: + type: Bool + default: true + supports_pattern: true + desc: + Automatically abort insecure (HTTP) downloads originating from secure + (HTTPS) pages. + + For per-domain settings, the relevant URL is the URL initiating the + download, not the URL the download itself is coming from. It's not + recommended to set this setting to false globally. + downloads.remove_finished: default: -1 type: @@ -1371,12 +1442,16 @@ editor.command: * `{line0}`: Same as `{line}`, but starting from index 0. * `{column0}`: Same as `{column}`, but starting from index 0. - editor.encoding: type: Encoding default: utf-8 desc: Encoding to use for the editor. +editor.remove_file: + default: true + type: Bool + desc: Delete the temporary file upon closing the editor. + ## fileselect fileselect.handler: @@ -1605,11 +1680,17 @@ hints.selectors: - '[role="link"]' - '[role="option"]' - '[role="button"]' + - '[role="tab"]' + - '[role="checkbox"]' + - '[role="menuitem"]' + - '[role="menuitemcheckbox"]' + - '[role="menuitemradio"]' + - '[role="treeitem"]' - '[ng-click]' - '[ngClick]' - '[data-ng-click]' - '[x-ng-click]' - - '[tabindex]' + - '[tabindex]:not([tabindex="-1"])' links: - 'a[href]' - 'area[href]' @@ -1753,10 +1834,10 @@ input.spatial_navigation: Enable spatial navigation. Spatial navigation consists in the ability to navigate between focusable - elements in a Web page, such as hyperlinks and form controls, by using - Left, Right, Up and Down arrow keys. For example, if the user presses the - Right key, heuristics determine whether there is an element he might be - trying to reach towards the right and which element he probably wants. + elements, such as hyperlinks and form controls, on a web page by using the + Left, Right, Up and Down arrow keys. For example, if a user presses the + Right key, heuristics determine whether there is an element they might be + trying to reach towards the right and which element they probably want. input.media_keys: default: true @@ -1770,6 +1851,29 @@ input.media_keys: On Linux, disabling this also disables Chromium's MPRIS integration. +input.match_counts: + default: true + type: Bool + desc: >- + Interpret number prefixes as counts for bindings. + + This enables for vi-like bindings that can be prefixed with a number to + indicate a count. + Disabling it allows for emacs-like bindings where number keys are passed + through (according to `input.forward_unbound_keys`) instead. + +input.mode_override: + default: null + type: + name: String + none_ok: true + valid_values: + - normal + - insert + - passthrough + supports_pattern: true + desc: Mode to change to when focusing on a tab/URL changes. + ## keyhint keyhint.blacklist: @@ -2134,6 +2238,7 @@ tabs.title.format: - title_sep - index - aligned_index + - relative_index - id - scroll_pos - host @@ -2153,6 +2258,7 @@ tabs.title.format: * `{index}`: Index of this tab. * `{aligned_index}`: Index of this tab padded with spaces to have the same width. + * `{relative_index}`: Index of this tab relative to the current tab. * `{id}`: Internal tab ID of this tab. * `{scroll_pos}`: Page scroll position. * `{host}`: Host of the current web page. @@ -2173,6 +2279,7 @@ tabs.title.format_pinned: - title_sep - index - aligned_index + - relative_index - id - scroll_pos - host @@ -3633,6 +3740,7 @@ bindings.default: <Ctrl-E>: edit-text <Shift-Ins>: insert-text -- {primary} <Escape>: mode-leave + <Shift-Escape>: fake-key <Escape> hint: <Return>: hint-follow <Ctrl-R>: hint --rapid links tab-bg @@ -3667,7 +3775,8 @@ bindings.default: <Ctrl-U>: rl-unix-line-discard <Ctrl-K>: rl-kill-line <Alt-D>: rl-kill-word - <Ctrl-W>: rl-unix-word-rubout + <Ctrl-W>: rl-rubout " " + <Ctrl-Shift-W>: rl-filename-rubout <Alt-Backspace>: rl-backward-kill-word <Ctrl-Y>: rl-yank <Ctrl-?>: rl-delete-char @@ -3692,7 +3801,8 @@ bindings.default: <Ctrl-U>: rl-unix-line-discard <Ctrl-K>: rl-kill-line <Alt-D>: rl-kill-word - <Ctrl-W>: rl-unix-word-rubout + <Ctrl-W>: rl-rubout " " + <Ctrl-Shift-W>: rl-filename-rubout <Alt-Backspace>: rl-backward-kill-word <Ctrl-?>: rl-delete-char <Ctrl-H>: rl-backward-delete-char @@ -3829,9 +3939,7 @@ bindings.commands: that single keys can't be bound in this mode. * passthrough: Similar to insert mode, but passes through all keypresses - except `<Escape>` to leave the mode. It might be useful to bind - `<Escape>` to some other key in this mode if you want to be able to send - an Escape key to the website as well. Note that single keys can't be + except `<Shift+Escape>` to leave the mode. Note that single keys can't be bound in this mode. * command: Entered when pressing the `:` key in order to enter a command. diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index 27a1001b5..58e5ad67a 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -19,8 +19,9 @@ """Exceptions related to config parsing.""" +import difflib import dataclasses -from typing import Any, Mapping, Optional, Sequence, Union +from typing import Any, Mapping, Optional, Sequence, Union, List from qutebrowser.utils import usertypes, log @@ -91,6 +92,7 @@ class NoOptionError(Error): """Raised when an option was not found.""" def __init__(self, option: str, *, + all_names: List[str] = None, deleted: bool = False, renamed: str = None) -> None: if deleted: @@ -98,6 +100,12 @@ class NoOptionError(Error): suffix = ' (this option was removed from qutebrowser)' elif renamed is not None: suffix = ' (this option was renamed to {!r})'.format(renamed) + elif all_names: + matches = difflib.get_close_matches(option, all_names, n=1) + if matches: + suffix = f' (did you mean {matches[0]!r}?)' + else: + suffix = '' else: suffix = '' diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index 6f0d0b13c..f2651d1cd 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -384,8 +384,8 @@ class YamlMigrations(QObject): changed = pyqtSignal() - def __init__(self, settings: _SettingsType, - parent: QObject = None) -> None: + # Note: settings is Any because it's not validated yet. + def __init__(self, settings: Any, parent: QObject = None) -> None: super().__init__(parent) self._settings = settings @@ -422,6 +422,12 @@ class YamlMigrations(QObject): false_value='load-insecurely', ask_value='ask', ) + self._migrate_renamed_bool( + old_name='content.javascript.can_access_clipboard', + new_name='content.javascript.clipboard', + true_value='access', + false_value='none', + ) for setting in ['colors.webpage.force_dark_color_scheme', 'colors.webpage.prefers_color_scheme_dark']: diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 6873af7cc..eef43ded4 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -109,6 +109,8 @@ class ValidValues: values: A list with the allowed untransformed values. descriptions: A dict with value/desc mappings. generate_docs: Whether to show the values in the docs. + others_permitted: Whether arbitrary values are permitted. + Used to show buttons in qute://settings. """ def __init__( @@ -119,12 +121,14 @@ class ValidValues: Tuple[str, Optional[str]], ], generate_docs: bool = True, + others_permitted: bool = False ) -> None: if not values: raise ValueError("ValidValues with no values makes no sense!") self.descriptions: DictType[str, str] = {} self.values: ListType[str] = [] self.generate_docs = generate_docs + self.others_permitted = others_permitted for value in values: if isinstance(value, str): # Value without description @@ -1638,7 +1642,9 @@ class Proxy(BaseType): super().__init__(none_ok=none_ok, completions=completions) self.valid_values = ValidValues( ('system', "Use the system wide proxy."), - ('none', "Don't use any proxy")) + ('none', "Don't use any proxy"), + others_permitted=True, + ) def to_py( self, diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py index c38ef5b01..9e7f2620d 100644 --- a/qutebrowser/config/qtargs.py +++ b/qutebrowser/config/qtargs.py @@ -260,9 +260,8 @@ def _qtwebengine_args( # Only actually available in Qt 5.12.5, but let's save another # check, as passing the option won't hurt. yield '--enable-in-process-stack-traces' - else: - if 'stack' not in namespace.debug_flags: - yield '--disable-in-process-stack-traces' + elif 'stack' not in namespace.debug_flags: + yield '--disable-in-process-stack-traces' lang_override = _get_lang_override( webengine_version=versions.webengine, @@ -322,12 +321,12 @@ def _qtwebengine_settings_args(versions: version.WebEngineVersions) -> Iterator[ '--force-webrtc-ip-handling-policy=' 'disable_non_proxied_udp', }, - 'qt.process_model': { + 'qt.chromium.process_model': { 'process-per-site-instance': None, 'process-per-site': '--process-per-site', 'single-process': '--single-process', }, - 'qt.low_end_device_mode': { + 'qt.chromium.low_end_device_mode': { 'auto': None, 'always': '--enable-low-end-device-mode', 'never': '--disable-low-end-device-mode', @@ -339,6 +338,11 @@ def _qtwebengine_settings_args(versions: version.WebEngineVersions) -> Iterator[ True: '--force-prefers-reduced-motion', False: None, }, + 'qt.chromium.sandboxing': { + 'enable-all': None, + 'disable-seccomp-bpf': '--disable-seccomp-filter-sandbox', + 'disable-all': '--no-sandbox', + } } qt_514_ver = utils.VersionNumber(5, 14) diff --git a/qutebrowser/config/stylesheet.py b/qutebrowser/config/stylesheet.py index 2927aec08..b08d67a3b 100644 --- a/qutebrowser/config/stylesheet.py +++ b/qutebrowser/config/stylesheet.py @@ -23,13 +23,14 @@ import functools from typing import Optional, FrozenSet from PyQt5.QtCore import pyqtSlot, QObject +from PyQt5.QtWidgets import QWidget from qutebrowser.config import config from qutebrowser.misc import debugcachestats from qutebrowser.utils import jinja, log -def set_register(obj: QObject, +def set_register(obj: QWidget, stylesheet: str = None, *, update: bool = True) -> None: """Set the stylesheet for an object. @@ -71,7 +72,7 @@ class _StyleSheetObserver(QObject): None. """ - def __init__(self, obj: QObject, + def __init__(self, obj: QWidget, stylesheet: Optional[str], update: bool) -> None: super().__init__() self._obj = obj @@ -81,7 +82,7 @@ class _StyleSheetObserver(QObject): if update: self.setParent(self._obj) if stylesheet is None: - self._stylesheet: str = obj.STYLESHEET + self._stylesheet: str = obj.STYLESHEET # type: ignore[attr-defined] else: self._stylesheet = stylesheet diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 7556d2b6d..779ed0b6b 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -164,14 +164,10 @@ class AbstractSettings: assert encoding is not usertypes.UNSET # unclear how to reset self._settings.setDefaultTextEncoding(encoding) - def _update_setting(self, setting: str, value: Any) -> bool: + def _update_setting(self, setting: str, value: Any) -> None: """Update the given setting/value. - Unknown settings are ignored. - - Return: - True if there was a change, False otherwise. - """ + Unknown settings are ignored.""" if setting in self._ATTRIBUTES: self.set_attribute(setting, value) elif setting in self._FONT_SIZES: @@ -180,7 +176,6 @@ class AbstractSettings: self.set_font_family(setting, value) elif setting == 'content.default_encoding': self.set_default_text_encoding(value) - return False def update_setting(self, setting: str) -> None: """Update the given setting.""" @@ -238,6 +233,7 @@ def user_agent(url: QUrl = None) -> str: def init(args: argparse.Namespace) -> None: """Initialize all QWeb(Engine)Settings.""" + utils.unused(args) if objects.backend == usertypes.Backend.QtWebEngine: from qutebrowser.browser.webengine import webenginesettings webenginesettings.init() diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py index 7ae45023b..793f131c8 100644 --- a/qutebrowser/extensions/loader.py +++ b/qutebrowser/extensions/loader.py @@ -21,12 +21,11 @@ import pkgutil import types -import sys import pathlib import importlib import argparse import dataclasses -from typing import Callable, Iterator, List, Optional, Set, Tuple +from typing import Callable, Iterator, List, Optional, Tuple from PyQt5.QtCore import pyqtSlot @@ -84,7 +83,7 @@ def add_module_info(module: types.ModuleType) -> ModuleInfo: # pylint: disable=protected-access if not hasattr(module, '__qute_module_info'): module.__qute_module_info = ModuleInfo() # type: ignore[attr-defined] - return module.__qute_module_info # type: ignore[attr-defined] + return module.__qute_module_info def load_components(*, skip_hooks: bool = False) -> None: @@ -95,22 +94,8 @@ def load_components(*, skip_hooks: bool = False) -> None: def walk_components() -> Iterator[ExtensionInfo]: """Yield ExtensionInfo objects for all modules.""" - if hasattr(sys, 'frozen'): - yield from _walk_pyinstaller() - else: - yield from _walk_normal() - - -def _on_walk_error(name: str) -> None: - raise ImportError("Failed to import {}".format(name)) - - -def _walk_normal() -> Iterator[ExtensionInfo]: - """Walk extensions when not using PyInstaller.""" for _finder, name, ispkg in pkgutil.walk_packages( - # Only packages have a __path__ attribute, - # but we're sure this is one. - path=components.__path__, # type: ignore[attr-defined] + path=components.__path__, prefix=components.__name__ + '.', onerror=_on_walk_error): if ispkg: @@ -123,23 +108,6 @@ def _walk_normal() -> Iterator[ExtensionInfo]: yield ExtensionInfo(name=name) -def _walk_pyinstaller() -> Iterator[ExtensionInfo]: - """Walk extensions when using PyInstaller. - - See https://github.com/pyinstaller/pyinstaller/issues/1905 - - Inspired by: - https://github.com/webcomics/dosage/blob/master/dosagelib/loader.py - """ - toc: Set[str] = set() - for importer in pkgutil.iter_importers('qutebrowser'): - if hasattr(importer, 'toc'): - toc |= importer.toc # type: ignore[union-attr] - for name in toc: - if name.startswith(components.__name__ + '.'): - yield ExtensionInfo(name=name) - - def _get_init_context() -> InitContext: """Get an InitContext object.""" return InitContext(data_dir=pathlib.Path(standarddir.data()), @@ -190,3 +158,7 @@ def _on_config_changed(changed_name: str) -> None: def init() -> None: config.instance.changed.connect(_on_config_changed) + + +def _on_walk_error(name: str) -> None: + raise ImportError("Failed to import {}".format(name)) diff --git a/qutebrowser/html/bindings.html b/qutebrowser/html/bindings.html index fe6913402..7bc441cbd 100644 --- a/qutebrowser/html/bindings.html +++ b/qutebrowser/html/bindings.html @@ -10,7 +10,7 @@ th { text-align:left; } {% block content %} <header><h1>{{ title }}</h1></header> {% for mode, binding in bindings.items() %} -<h2>{{ mode | capitalize }} mode</h2> +<h2 id="{{ mode }}">{{ mode | capitalize }} mode</h2> <table> <tr> <th>Key</th> diff --git a/qutebrowser/html/settings.html b/qutebrowser/html/settings.html index 44824eeac..f89aaa610 100644 --- a/qutebrowser/html/settings.html +++ b/qutebrowser/html/settings.html @@ -13,22 +13,145 @@ var cset = function(option, value) { {% endblock %} {% block style %} -table { border: 1px solid grey; border-collapse: collapse; } -pre { margin: 2px; } -th, td { border: 1px solid grey; padding: 0px 5px; } -th { background: lightgrey; } -th pre { color: grey; text-align: left; } -input { width: 98%; } -.setting { width: 75%; } -.value { width: 25%; text-align: center; } -.noscript, .noscript-text { color:red; } -.noscript-text { margin-bottom: 5cm; } -.option_description { margin: .5ex 0; color: grey; font-size: 80%; font-style: italic; white-space: pre-line; } +table { + border-spacing: 10px; +} + +tbody tr:nth-child(odd) { + background: #eaf4fb; +} + +pre { + margin: 2px; +} + +th { + padding: 10px; + border-radius: 5px; + background: #a6dfff; + text-align: left; + font-weight: normal; + font-size: 1.5rem; + color: #084c88; +} + +td { + padding: 5px 5px; +} + +th pre { + color: grey; + text-align: left; +} + +input { + padding: 8px; + width: 98%; + box-sizing: border-box; + border-radius: 4px; + border: 1px solid #01cdd0; + font-size: 0.9rem; + font-family: DejaVu, serif; +} + +input:focus { + outline: none; + border: 2px solid #7a589ea6; +} + +input[type="radio"] { + position: absolute; /* Positions the radio button relative to the edges of its containing element */ + -webkit-appearance: none; /* Removes its native styling */ + width: min-content; + margin: 0; + border: none; + cursor: pointer; +} + +label { + cursor: pointer; + margin-bottom: 2px; + padding: 5px 10px; + border-radius: 5px; + background-color: #dddddd; + color: #666666; +} + +input[type="radio"]:checked + label { + background-color: #a6dfff; + color: #084c88; +} + +.radio-button { + position: relative; /* The absolutely positioned element inside this tag (the radio button) gets positioned relative to it. */ + display: inline-flex; + margin: 3px 1px; +} + +.setting { + width: 60%; +} + +.value { + width: 25%; + text-align: center; +} + +.valid-value { + text-align: center; +} + +.noscript, .noscript-text { + color: red; +} + +.noscript-text { + margin-bottom: 5cm; +} + +.option-description { + margin: .5ex 0; + color: #635d5dcf; + font-size: 80%; + font-style: italic; +} + +.option-description p { + margin: 0; +} + +.long-description { + white-space: pre-line; +} + +details summary > * { + display: inline; +} + +details[open] .details { + display: none; +} + +summary { + margin: .5ex 0; + width: fit-content; + color: #1887c5; + outline: none; + font-size: 105%; + cursor: pointer; +} + +summary .short-description { + color: #635d5dcf; +} + +summary::selection { + background-color: inherit; +} {% endblock %} {% block content %} <noscript><h1 class="noscript">View Only</h1><p class="noscript-text">Changing settings requires javascript to be enabled!</p></noscript> -<header><h1>{{ title }}</h1></header> <table> <tr> <th>Setting</th> @@ -36,19 +159,52 @@ input { width: 98%; } </tr> {% for option in configdata.DATA.values()|sort(attribute='name') if not option.no_autoconfig %} <tr> + {% set loopIndex = loop.index0 %} <!-- FIXME: convert to string properly --> - <td class="setting">{{ option.name }} (Current: {{ confget(option.name) | string |truncate(100) }}) + <td class="setting">{{ option.name }} {% if option.description %} - <p class="option_description">{{ option.description|e }}</p> + {% set description = option.description.split('\n', 1) %} + <div class="option-description"> + {% if description|length > 1 %} + <details> + <summary> + <p class="short-description">{{ description[0]|e }}</p> + <span class="details">Details</span> + </summary> + <p class="long-description">{{ description[1]|e }}</p> + </details> + {% else %} + <p>{{ description[0]|e }}</p> + {% endif %} + </div> {% endif %} </td> - <td class="value"> - <input type="text" - id="input-{{ option.name }}" - onblur="cset('{{ option.name }}', this.value)" - value="{{ confget(option.name) }}"> - </input> - </td> + {% set valid_values = option.typ.valid_values %} + {% if valid_values is not none and not valid_values.others_permitted %} + <td class="valid-value"> + {% for value in valid_values.values %} + <div class="radio-button"> + <input type="radio" id="input-{{ option.name }}-{{ loop.index0 }}" + name="{{ option.name }}" value="{{ value }}" + onclick="cset('{{ option.name }}', this.value)" + {% if confget(option.name) == value %} + checked + {% endif %}> + <label for="input-{{ option.name }}-{{ loop.index0 }}"> + {{ value }} + </label> + </div> + {% endfor %} + </td> + {% else %} + <td class="value"> + <input type="text" + id="input-{{ option.name }}" + onblur="cset('{{ option.name }}', this.value)" + value="{{ confget(option.name) }}"> + </input> + </td> + {% endif %} </tr> {% endfor %} </table> diff --git a/qutebrowser/html/warning-webkit.html b/qutebrowser/html/warning-webkit.html index 975f98c1b..f5cf9bf01 100644 --- a/qutebrowser/html/warning-webkit.html +++ b/qutebrowser/html/warning-webkit.html @@ -41,8 +41,8 @@ hopefully help.</p> notification support was added for Qt 5.13.0.</p> <p><b>Resource usage</b>: qutebrowser v1.5.0 added the <span -class="mono">qt.process_model</span> and <span -class="mono">qt.low_end_device_mode</span> settings which can be used to +class="mono">qt.chromium.process_model</span> and <span +class="mono">qt.chromium.low_end_device_mode</span> settings which can be used to decrease the resource usage of QtWebEngine (but come with other drawbacks).</p> <p><b>Not trusting Google</b>: Various people have checked the connections made @@ -78,7 +78,7 @@ security fixes took months to arrive there). You might be better off choosing an method</a>.</p> <p><b>White flashing between loads with a custom stylesheet</b>: This doesn't -seem to happen with <span class="mono">qt.process_model = single-process</span> +seem to happen with <span class="mono">qt.chromium.process_model = single-process</span> set. However, note that that setting comes with decreased security and stability, but QtWebKit doesn't have any process isolation at all.</p> {% endblock %} diff --git a/icons/qutebrowser-128x128.png b/qutebrowser/icons/qutebrowser-128x128.png Binary files differindex ca88ccc42..ca88ccc42 100644 --- a/icons/qutebrowser-128x128.png +++ b/qutebrowser/icons/qutebrowser-128x128.png diff --git a/icons/qutebrowser-16x16.png b/qutebrowser/icons/qutebrowser-16x16.png Binary files differindex 062fe402c..062fe402c 100644 --- a/icons/qutebrowser-16x16.png +++ b/qutebrowser/icons/qutebrowser-16x16.png diff --git a/icons/qutebrowser-24x24.png b/qutebrowser/icons/qutebrowser-24x24.png Binary files differindex 6aa2b5cd3..6aa2b5cd3 100644 --- a/icons/qutebrowser-24x24.png +++ b/qutebrowser/icons/qutebrowser-24x24.png diff --git a/icons/qutebrowser-256x256.png b/qutebrowser/icons/qutebrowser-256x256.png Binary files differindex 705d314fc..705d314fc 100644 --- a/icons/qutebrowser-256x256.png +++ b/qutebrowser/icons/qutebrowser-256x256.png diff --git a/icons/qutebrowser-32x32.png b/qutebrowser/icons/qutebrowser-32x32.png Binary files differindex c03d91092..c03d91092 100644 --- a/icons/qutebrowser-32x32.png +++ b/qutebrowser/icons/qutebrowser-32x32.png diff --git a/icons/qutebrowser-48x48.png b/qutebrowser/icons/qutebrowser-48x48.png Binary files differindex 1dd9e79a5..1dd9e79a5 100644 --- a/icons/qutebrowser-48x48.png +++ b/qutebrowser/icons/qutebrowser-48x48.png diff --git a/icons/qutebrowser-512x512.png b/qutebrowser/icons/qutebrowser-512x512.png Binary files differindex 08a273507..08a273507 100644 --- a/icons/qutebrowser-512x512.png +++ b/qutebrowser/icons/qutebrowser-512x512.png diff --git a/icons/qutebrowser-64x64.png b/qutebrowser/icons/qutebrowser-64x64.png Binary files differindex 72dc3febb..72dc3febb 100644 --- a/icons/qutebrowser-64x64.png +++ b/qutebrowser/icons/qutebrowser-64x64.png diff --git a/icons/qutebrowser-96x96.png b/qutebrowser/icons/qutebrowser-96x96.png Binary files differindex bc24982c5..bc24982c5 100644 --- a/icons/qutebrowser-96x96.png +++ b/qutebrowser/icons/qutebrowser-96x96.png diff --git a/icons/qutebrowser-all.svg b/qutebrowser/icons/qutebrowser-all.svg index da9ab4d6d..da9ab4d6d 100644 --- a/icons/qutebrowser-all.svg +++ b/qutebrowser/icons/qutebrowser-all.svg diff --git a/icons/qutebrowser-favicon.svg b/qutebrowser/icons/qutebrowser-favicon.svg index 0cceab56c..0cceab56c 100644 --- a/icons/qutebrowser-favicon.svg +++ b/qutebrowser/icons/qutebrowser-favicon.svg diff --git a/icons/qutebrowser.icns b/qutebrowser/icons/qutebrowser.icns Binary files differindex 928b6ca89..928b6ca89 100644 --- a/icons/qutebrowser.icns +++ b/qutebrowser/icons/qutebrowser.icns diff --git a/icons/qutebrowser.ico b/qutebrowser/icons/qutebrowser.ico Binary files differindex 42419d944..42419d944 100644 --- a/icons/qutebrowser.ico +++ b/qutebrowser/icons/qutebrowser.ico diff --git a/icons/qutebrowser.svg b/qutebrowser/icons/qutebrowser.svg index b26c80c2b..b26c80c2b 100644 --- a/icons/qutebrowser.svg +++ b/qutebrowser/icons/qutebrowser.svg diff --git a/icons/qutebrowser.xpm b/qutebrowser/icons/qutebrowser.xpm index 04e183154..04e183154 100644 --- a/icons/qutebrowser.xpm +++ b/qutebrowser/icons/qutebrowser.xpm diff --git a/qutebrowser/javascript/greasemonkey_wrapper.js b/qutebrowser/javascript/greasemonkey_wrapper.js index 7bfabc635..ced4e1063 100644 --- a/qutebrowser/javascript/greasemonkey_wrapper.js +++ b/qutebrowser/javascript/greasemonkey_wrapper.js @@ -77,6 +77,9 @@ if ("onerror" in details) { oXhr.onerror = function () { details.onerror(oXhr); }; } + if ("overrideMimeType" in details) { + oXhr.overrideMimeType(details.overrideMimeType); + } oXhr.open(details.method, details.url, true); @@ -107,6 +110,22 @@ } } + // Based on GreaseMonkey: + // https://github.com/greasemonkey/greasemonkey/blob/4.11/src/bg/api-provider-source.js#L232-L249 + function GM_setClipboard(text) { + function onCopy(event) { + document.removeEventListener('copy', onCopy, true); + + event.stopImmediatePropagation(); + event.preventDefault(); + + event.clipboardData.setData('text/plain', text); + } + + document.addEventListener('copy', onCopy, true); + document.execCommand('copy'); + } + // Stub these two so that the gm4 polyfill script doesn't try to // create broken versions as attributes of window. function GM_getResourceText(caption, commandFunc, accessKey) { @@ -123,6 +142,7 @@ const entries = { 'log': GM_log, 'addStyle': GM_addStyle, + 'setClipboard': GM_setClipboard, 'deleteValue': GM_deleteValue, 'getValue': GM_getValue, 'listValues': GM_listValues, diff --git a/qutebrowser/javascript/quirks/globalthis.user.js b/qutebrowser/javascript/quirks/globalthis.user.js index 103681fda..b87a956e5 100644 --- a/qutebrowser/javascript/quirks/globalthis.user.js +++ b/qutebrowser/javascript/quirks/globalthis.user.js @@ -1,6 +1,8 @@ // ==UserScript== // @include https://www.reddit.com/* // @include https://open.spotify.com/* +// @include https://*.stackexchange.com/* +// @include https://stackoverflow.com/* // @include https://test.qutebrowser.org/* // ==/UserScript== diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 7e688dab1..4db1d5d76 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -254,6 +254,9 @@ class BaseKeyParser(QObject): def _match_count(self, sequence: keyutils.KeySequence, dry_run: bool) -> bool: """Try to match a key as count.""" + if not config.val.input.match_counts: + return False + txt = str(sequence[-1]) # To account for sequences changed above. if (txt in string.digits and self._supports_count and not (not self._count and txt == '0')): diff --git a/qutebrowser/keyinput/eventfilter.py b/qutebrowser/keyinput/eventfilter.py index 4d016bea6..f0d85b0ec 100644 --- a/qutebrowser/keyinput/eventfilter.py +++ b/qutebrowser/keyinput/eventfilter.py @@ -35,7 +35,7 @@ class EventFilter(QObject): Attributes: _activated: Whether the EventFilter is currently active. - _handlers; A {QEvent.Type: callable} dict with the handlers for an + _handlers: A {QEvent.Type: callable} dict with the handlers for an event. """ diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index 6bd8c99b8..0362e09f3 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -156,7 +156,7 @@ def _assert_plain_key(key: Qt.Key) -> None: def _assert_plain_modifier(key: _ModifierType) -> None: """Make sure this is a modifier without a key mixed in.""" mask = Qt.KeyboardModifierMask - assert not key & ~mask, hex(key) # type: ignore[operator] + assert not key & ~mask, hex(key) def _is_printable(key: Qt.Key) -> bool: @@ -254,9 +254,9 @@ def _modifiers_to_string(modifiers: _ModifierType) -> str: modifier. """ _assert_plain_modifier(modifiers) - altgr = Qt.GroupSwitchModifier - if modifiers & altgr: # type: ignore[operator] - modifiers &= ~altgr # type: ignore[operator, assignment] + altgr = cast(Qt.KeyboardModifiers, Qt.GroupSwitchModifier) + if modifiers & altgr: + modifiers &= ~altgr result = 'AltGr+' else: result = '' @@ -416,7 +416,7 @@ class KeyInfo: return '' text = QKeySequence(self.key).toString() - if not self.modifiers & Qt.ShiftModifier: # type: ignore[operator] + if not self.modifiers & Qt.ShiftModifier: text = text.lower() return text @@ -473,7 +473,7 @@ class KeySequence: """Iterate over KeyInfo objects.""" for key_and_modifiers in self._iter_keys(): key = Qt.Key(int(key_and_modifiers) & ~Qt.KeyboardModifierMask) - modifiers = Qt.KeyboardModifiers( # type: ignore[call-overload] + modifiers = Qt.KeyboardModifiers( int(key_and_modifiers) & Qt.KeyboardModifierMask) yield KeyInfo(key=key, modifiers=modifiers) @@ -619,14 +619,15 @@ class KeySequence: # (or "Cmd") in a key binding name to actually represent what's on the # keyboard. if utils.is_mac: + # FIXME:qt6 Reevaluate the type ignores below if modifiers & Qt.ControlModifier and modifiers & Qt.MetaModifier: pass elif modifiers & Qt.ControlModifier: modifiers &= ~Qt.ControlModifier - modifiers |= Qt.MetaModifier + modifiers |= Qt.MetaModifier # type: ignore[assignment] elif modifiers & Qt.MetaModifier: modifiers &= ~Qt.MetaModifier - modifiers |= Qt.ControlModifier + modifiers |= Qt.ControlModifier # type: ignore[assignment] keys = list(self._iter_keys()) keys.append(key | int(modifiers)) @@ -656,7 +657,6 @@ class KeySequence: @classmethod def parse(cls, keystr: str) -> 'KeySequence': """Parse a keystring like <Ctrl-x> or xyz and return a KeySequence.""" - # pylint: disable=protected-access new = cls() strings = list(_parse_keystring(keystr)) for sub in utils.chunk(strings, cls._MAX_LEN): @@ -666,6 +666,5 @@ class KeySequence: if keystr: assert new, keystr - # pylint: disable=protected-access new._validate(keystr) return new diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 3c47fafe3..24c701401 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -37,6 +37,7 @@ from qutebrowser.misc import objects INPUT_MODES = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] PROMPT_MODES = [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno] +# FIXME:mypy TypedDict? ParserDictType = MutableMapping[usertypes.KeyMode, basekeyparser.BaseKeyParser] diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index d0723742a..b247da632 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -92,7 +92,7 @@ def raise_window(window, alert=True): window.setWindowState(window.windowState() | Qt.WindowActive) window.raise_() # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-69568 - QCoreApplication.processEvents( # type: ignore[call-overload] + QCoreApplication.processEvents( QEventLoop.ExcludeUserInputEvents | QEventLoop.ExcludeSocketNotifiers) if not sip.isdeleted(window): @@ -249,8 +249,8 @@ class MainWindow(QWidget): log.init.debug("Initializing modes...") modeman.init(win_id=self.win_id, parent=self) - self._commandrunner = runners.CommandRunner(self.win_id, - partial_match=True) + self._commandrunner = runners.CommandRunner( + self.win_id, partial_match=True, find_similar=True) self._keyhint = keyhintwidget.KeyHintView(self.win_id, self) self._add_overlay(self._keyhint, self._keyhint.update_geometry) @@ -561,11 +561,11 @@ class MainWindow(QWidget): def _set_decoration(self, hidden): """Set the visibility of the window decoration via Qt.""" - window_flags: int = Qt.Window + window_flags = cast(Qt.WindowFlags, Qt.Window) refresh_window = self.isVisible() if hidden: window_flags |= Qt.CustomizeWindowHint | Qt.NoDropShadowWindowHint - self.setWindowFlags(cast(Qt.WindowFlags, window_flags)) + self.setWindowFlags(window_flags) if refresh_window: self.show() @@ -574,9 +574,7 @@ class MainWindow(QWidget): if not config.val.content.fullscreen.window: if on: self.state_before_fullscreen = self.windowState() - self.setWindowState( - Qt.WindowFullScreen | # type: ignore[arg-type] - self.state_before_fullscreen) # type: ignore[operator] + self.setWindowState(Qt.WindowFullScreen | self.state_before_fullscreen) elif self.isFullScreen(): self.setWindowState(self.state_before_fullscreen) log.misc.debug('on: {}, state before fullscreen: {}'.format( @@ -602,7 +600,7 @@ class MainWindow(QWidget): super().resizeEvent(e) self._update_overlay_geometries() self._downloadview.updateGeometry() - self.tabbed_browser.widget.tabBar().refresh() + self.tabbed_browser.widget.tab_bar().refresh() def showEvent(self, e): """Extend showEvent to register us as the last-visible-main-window. @@ -620,13 +618,15 @@ class MainWindow(QWidget): True if closing is okay, False if a closeEvent should be ignored. """ tab_count = self.tabbed_browser.widget.count() + window_count = len(objreg.window_registry) download_count = self._download_model.running_downloads() quit_texts = [] # Ask if multiple-tabs are open if 'multiple-tabs' in config.val.confirm_quit and tab_count > 1: quit_texts.append("{} tabs are open.".format(tab_count)) - # Ask if multiple downloads running - if 'downloads' in config.val.confirm_quit and download_count > 0: + # Ask if downloads running + if ('downloads' in config.val.confirm_quit and download_count > 0 and + window_count <= 1): quit_texts.append("{} {} running.".format( download_count, "download is" if download_count == 1 else "downloads are")) diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index c8cbe572b..f7a04bee0 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -536,9 +536,11 @@ class _BasePrompt(QWidget): self.KEY_MODE.name) labels = [] + has_bindings = False for cmd, text in self._allowed_commands(): bindings = all_bindings.get(cmd, []) if bindings: + has_bindings = True binding = None preferred = ['<enter>', '<escape>'] for pref in preferred: @@ -547,8 +549,11 @@ class _BasePrompt(QWidget): if binding is None: binding = bindings[0] key_label = QLabel('<b>{}</b>'.format(html.escape(binding))) - text_label = QLabel(text) - labels.append((key_label, text_label)) + else: + key_label = QLabel(f'<b>unbound</b> (<tt>{html.escape(cmd)}</tt>)') + + text_label = QLabel(text) + labels.append((key_label, text_label)) for i, (key_label, text_label) in enumerate(labels): self._key_grid.addWidget(key_label, i, 0) @@ -559,6 +564,14 @@ class _BasePrompt(QWidget): self._vbox.addLayout(self._key_grid) + if not has_bindings: + label = QLabel( + "<b>Note:</b> You seem to have unbound all keys for this prompt " + f"(<tt>{self.KEY_MODE.name}</tt> key mode)." + "<br/>Run <tt>qutebrowser :CMD</tt> with a command from above to " + "close this prompt, then fix this in your config.") + self._vbox.addWidget(label) + def _check_save_support(self, save): if save: raise UnsupportedOperationError("Saving answers is only possible " @@ -771,7 +784,7 @@ class FilenamePrompt(_BasePrompt): selmodel.setCurrentIndex( idx, - QItemSelectionModel.ClearAndSelect | # type: ignore[arg-type] + QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self._insert_path(idx, clicked=False) @@ -795,7 +808,7 @@ class DownloadFilenamePrompt(FilenamePrompt): def __init__(self, question, parent=None): super().__init__(question, parent) self._file_model.setFilter( - QDir.AllDirs | QDir.Drives | QDir.NoDot) # type: ignore[arg-type] + QDir.AllDirs | QDir.Drives | QDir.NoDotAndDotDot) def accept(self, value=None, save=False): done = super().accept(value, save) @@ -818,6 +831,7 @@ class DownloadFilenamePrompt(FilenamePrompt): cmds = [ ('prompt-accept', 'Accept'), ('mode-leave', 'Abort'), + ('rl-filename-rubout', "Go to parent directory"), ('prompt-open-download', "Open download"), ('prompt-open-download --pdfjs', "Open download via PDF.js"), ('prompt-yank', "Yank URL"), diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index f4b81b7e6..ae33a386a 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -22,8 +22,7 @@ import enum import dataclasses -from PyQt5.QtCore import (pyqtSignal, pyqtSlot, # type: ignore[attr-defined] - pyqtProperty, Qt, QSize, QTimer) +from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from qutebrowser.browser import browsertab @@ -314,7 +313,7 @@ class StatusBar(QWidget): padding = config.val.statusbar.padding self._hbox.setContentsMargins(padding.left, 0, padding.right, 0) - @pyqtProperty('QStringList') + @pyqtProperty('QStringList') # type: ignore[type-var] def color_flags(self): """Getter for self.color_flags, so it can be used as Qt property.""" return self._color_flags.to_stringlist() diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index 92408d34f..95076380a 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -19,6 +19,7 @@ """The commandline in the statusbar.""" +from typing import Optional from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QSize from PyQt5.QtGui import QKeyEvent @@ -240,7 +241,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): self.clear_completion_selection.emit() self.hide_completion.emit() - def setText(self, text: str) -> None: + def setText(self, text: Optional[str]) -> None: """Extend setText to set prefix and make sure the prompt is ok.""" if not text: pass @@ -276,7 +277,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): text = self.text() if not text: text = 'x' - width = self.fontMetrics().width(text) + width = self.fontMetrics().boundingRect(text).width() return QSize(width, height) @pyqtSlot() diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index 99818e284..a971d76aa 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -21,8 +21,7 @@ import enum -from PyQt5.QtCore import (pyqtSlot, pyqtProperty, # type: ignore[attr-defined] - QUrl) +from PyQt5.QtCore import pyqtSlot, pyqtProperty, QUrl from qutebrowser.mainwindow.statusbar import textbase from qutebrowser.config import stylesheet @@ -92,7 +91,7 @@ class UrlText(textbase.TextBase): self._normal_url = None self._normal_url_type = UrlType.normal - @pyqtProperty(str) + @pyqtProperty(str) # type: ignore[type-var] def urltype(self): """Getter for self.urltype, so it can be used as Qt property. diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index e081284ee..68b4adfdb 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -28,7 +28,7 @@ from typing import ( Any, Deque, List, Mapping, MutableMapping, MutableSequence, Optional, Tuple) from PyQt5.QtWidgets import QSizePolicy, QWidget, QApplication -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl, QPoint from qutebrowser.config import config from qutebrowser.keyinput import modeman @@ -73,14 +73,14 @@ class TabDeque: size = config.val.tabs.focus_stack_size if size < 0: size = None - self._stack: Deque[weakref.ReferenceType[QWidget]] = collections.deque( - maxlen=size) + self._stack: Deque[weakref.ReferenceType[browsertab.AbstractTab]] = ( + collections.deque(maxlen=size)) # Items that have been removed from the primary stack. - self._stack_deleted: List[weakref.ReferenceType[QWidget]] = [] + self._stack_deleted: List[weakref.ReferenceType[browsertab.AbstractTab]] = [] self._ignore_next = False self._keep_deleted_next = False - def on_switch(self, old_tab: QWidget) -> None: + def on_switch(self, old_tab: browsertab.AbstractTab) -> None: """Record tab switch events.""" if self._ignore_next: self._ignore_next = False @@ -92,24 +92,29 @@ class TabDeque: self._keep_deleted_next = False self._stack.append(tab) - def prev(self, cur_tab: QWidget) -> QWidget: + def prev(self, cur_tab: browsertab.AbstractTab) -> browsertab.AbstractTab: """Get the 'previous' tab in the stack. Throws IndexError on failure. """ - tab: Optional[QWidget] = None + tab: Optional[browsertab.AbstractTab] = None while tab is None or tab.pending_removal or tab is cur_tab: tab = self._stack.pop()() self._stack_deleted.append(weakref.ref(cur_tab)) self._ignore_next = True return tab - def next(self, cur_tab: QWidget, *, keep_overflow: bool = True) -> QWidget: + def next( + self, + cur_tab: browsertab.AbstractTab, + *, + keep_overflow: bool = True, + ) -> browsertab.AbstractTab: """Get the 'next' tab in the stack. Throws IndexError on failure. """ - tab: Optional[QWidget] = None + tab: Optional[browsertab.AbstractTab] = None while tab is None or tab.pending_removal or tab is cur_tab: tab = self._stack_deleted.pop()() # On next tab-switch, current tab will be added to stack as normal. @@ -118,7 +123,7 @@ class TabDeque: self._keep_deleted_next = True return tab - def last(self, cur_tab: QWidget) -> QWidget: + def last(self, cur_tab: browsertab.AbstractTab) -> browsertab.AbstractTab: """Get the last tab. Throws IndexError on failure. @@ -216,13 +221,18 @@ class TabbedBrowser(QWidget): self.widget.tabCloseRequested.connect(self.on_tab_close_requested) self.widget.new_tab_requested.connect(self.tabopen) self.widget.currentChanged.connect(self._on_current_changed) - self.cur_fullscreen_requested.connect(self.widget.tabBar().maybe_hide) + self.cur_fullscreen_requested.connect(self.widget.tab_bar().maybe_hide) + self.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # load_finished instead of load_started as WORKAROUND for # https://bugreports.qt.io/browse/QTBUG-65223 self.cur_load_finished.connect(self._leave_modes_on_load) + # handle mode_override + self.current_tab_changed.connect(lambda tab: self._mode_override(tab.url())) + self.cur_url_changed.connect(self._mode_override) + # This init is never used, it is immediately thrown away in the next # line. self.undo_stack: UndoStackType = collections.deque() @@ -231,8 +241,8 @@ class TabbedBrowser(QWidget): self._now_focused = None self.search_text = None self.search_options: Mapping[str, Any] = {} - self._local_marks: MutableMapping[QUrl, MutableMapping[str, int]] = {} - self._global_marks: MutableMapping[str, Tuple[int, QUrl]] = {} + self._local_marks: MutableMapping[QUrl, MutableMapping[str, QPoint]] = {} + self._global_marks: MutableMapping[str, Tuple[QPoint, QUrl]] = {} self.default_window_icon = self.widget.window().windowIcon() self.is_private = private self.tab_deque = TabDeque() @@ -369,6 +379,25 @@ class TabbedBrowser(QWidget): tab.history_item_triggered.connect( history.web_history.add_from_tab) + def _current_tab(self) -> browsertab.AbstractTab: + """Get the current browser tab. + + Note: The assert ensures the current tab is never None. + """ + tab = self.widget.currentWidget() + assert isinstance(tab, browsertab.AbstractTab), tab + return tab + + def _tab_by_idx(self, idx: int) -> Optional[browsertab.AbstractTab]: + """Get a browser tab by index. + + If no tab was found at the given index, None is returned. + """ + tab = self.widget.widget(idx) + if tab is not None: + assert isinstance(tab, browsertab.AbstractTab), tab + return tab + def current_url(self): """Get the URL of the current tab. @@ -406,15 +435,16 @@ class TabbedBrowser(QWidget): else: yes_action() - def close_tab(self, tab, *, add_undo=True, new_undo=True): + def close_tab(self, tab, *, add_undo=True, new_undo=True, transfer=False): """Close a tab. Args: tab: The QWebView to be closed. add_undo: Whether the tab close can be undone. new_undo: Whether the undo entry should be a new item in the stack. + transfer: Whether the tab is closing because it is moving to a new window. """ - if config.val.tabs.tabs_are_windows: + if config.val.tabs.tabs_are_windows or transfer: last_close = 'close' else: last_close = config.val.tabs.last_close @@ -498,25 +528,27 @@ class TabbedBrowser(QWidget): ] only_one_tab_open = self.widget.count() == 1 if only_one_tab_open and last_close_replaces: - no_history = len(self.widget.widget(0).history) == 1 + tab = self._tab_by_idx(0) + assert tab is not None + no_history = len(tab.history) == 1 urls = { 'blank': QUrl('about:blank'), 'startpage': config.val.url.start_pages[0], 'default-page': config.val.url.default_page, } - first_tab_url = self.widget.widget(0).url() + first_tab_url = tab.url() last_close_urlstr = urls[last_close].toString().rstrip('/') first_tab_urlstr = first_tab_url.toString().rstrip('/') last_close_url_used = first_tab_urlstr == last_close_urlstr - use_current_tab = (only_one_tab_open and no_history and - last_close_url_used) + use_current_tab = no_history and last_close_url_used entries = self.undo_stack[-depth] del self.undo_stack[-depth] for entry in reversed(entries): if use_current_tab: - newtab = self.widget.widget(0) + newtab = self._tab_by_idx(0) + assert newtab is not None use_current_tab = False else: newtab = self.tabopen(background=False, idx=entry.index) @@ -536,14 +568,14 @@ class TabbedBrowser(QWidget): if newtab or self.widget.currentWidget() is None: self.tabopen(url, background=False) else: - self.widget.currentWidget().load_url(url) + self._current_tab().load_url(url) @pyqtSlot(int) def on_tab_close_requested(self, idx): """Close a tab via an index.""" - tab = self.widget.widget(idx) + tab = self._tab_by_idx(idx) if tab is None: - log.webview.debug( # type: ignore[unreachable] + log.webview.debug( "Got invalid tab {} for index {}!".format(tab, idx)) return self.tab_close_prompt_if_pinned( @@ -697,10 +729,9 @@ class TabbedBrowser(QWidget): """ if tab.data.keep_icon: tab.data.keep_icon = False - else: - if (config.cache['tabs.tabs_are_windows'] and - tab.data.should_show_icon()): - self.widget.window().setWindowIcon(self.default_window_icon) + elif (config.cache['tabs.tabs_are_windows'] and + tab.data.should_show_icon()): + self.widget.window().setWindowIcon(self.default_window_icon) @pyqtSlot() def _on_load_status_changed(self, tab): @@ -777,6 +808,23 @@ class TabbedBrowser(QWidget): if not self.widget.page_title(idx): self.widget.set_page_title(idx, url.toDisplayString()) + def _mode_override(self, url: QUrl) -> None: + """Override mode if url matches pattern. + + Args: + url: The QUrl to match for + """ + if not url.isValid(): + return + mode = config.instance.get('input.mode_override', url=url) + if mode: + log.modes.debug(f"Mode change to {mode} triggered for url {url}") + modeman.enter( + self._win_id, + usertypes.KeyMode[mode], + reason='mode_override', + ) + @pyqtSlot(browsertab.AbstractTab) def _on_icon_changed(self, tab): """Set the icon of a tab. @@ -800,6 +848,7 @@ class TabbedBrowser(QWidget): mode in modeman.INPUT_MODES): tab = self.widget.currentWidget() if tab is not None: + assert isinstance(tab, browsertab.AbstractTab), tab tab.data.input_mode = mode @pyqtSlot(usertypes.KeyMode) @@ -813,6 +862,7 @@ class TabbedBrowser(QWidget): widget)) widget.setFocus() if config.val.tabs.mode_on_change == 'restore': + assert isinstance(widget, browsertab.AbstractTab), widget widget.data.input_mode = usertypes.KeyMode.normal @pyqtSlot(int) @@ -822,9 +872,9 @@ class TabbedBrowser(QWidget): if idx == -1 or self.is_shutting_down: # closing the last tab (before quitting) or shutting down return - tab = self.widget.widget(idx) + tab = self._tab_by_idx(idx) if tab is None: - log.webview.debug( # type: ignore[unreachable] + log.webview.debug( "on_current_changed got called with invalid index {}" .format(idx)) return @@ -1002,7 +1052,7 @@ class TabbedBrowser(QWidget): if key != "'": message.error("Failed to set mark: url invalid") return - point = self.widget.currentWidget().scroller.pos_px() + point = self._current_tab().scroller.pos_px() if key.isupper(): self._global_marks[key] = point, url @@ -1023,7 +1073,7 @@ class TabbedBrowser(QWidget): except qtutils.QtValueError: urlkey = None - tab = self.widget.currentWidget() + tab = self._current_tab() if key.isupper(): if key in self._global_marks: diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 7983127d5..bbd5af8a0 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -22,13 +22,13 @@ import functools import contextlib import dataclasses -from typing import Optional, cast +from typing import Optional, Dict, Any from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint, QTimer, QUrl) from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle, QStyle, QStylePainter, QStyleOptionTab, - QStyleFactory, QWidget) + QStyleFactory) from PyQt5.QtGui import QIcon, QPalette, QColor from qutebrowser.utils import qtutils, objreg, utils, usertypes, log @@ -76,17 +76,30 @@ class TabWidget(QTabWidget): @config.change_filter('tabs') def _init_config(self): """Initialize attributes based on the config.""" - tabbar = self.tabBar() self.setMovable(True) self.setTabsClosable(False) position = config.val.tabs.position selection_behavior = config.val.tabs.select_on_remove self.setTabPosition(position) - tabbar.vertical = position in [ # type: ignore[attr-defined] - QTabWidget.West, QTabWidget.East] + + tabbar = self.tab_bar() + tabbar.vertical = position in [QTabWidget.West, QTabWidget.East] tabbar.setSelectionBehaviorOnRemove(selection_behavior) tabbar.refresh() + def tab_bar(self) -> "TabBar": + """Get the TabBar for this TabWidget.""" + bar = self.tabBar() + assert isinstance(bar, TabBar), bar + return bar + + def _tab_by_idx(self, idx: int) -> Optional[browsertab.AbstractTab]: + """Get the tab at the given index.""" + tab = self.widget(idx) + if tab is not None: + assert isinstance(tab, browsertab.AbstractTab), tab + return tab + def set_tab_indicator_color(self, idx, color): """Set the tab indicator color. @@ -94,17 +107,17 @@ class TabWidget(QTabWidget): idx: The tab index. color: A QColor. """ - bar = self.tabBar() + bar = self.tab_bar() bar.set_tab_data(idx, 'indicator-color', color) bar.update(bar.tabRect(idx)) def tab_indicator_color(self, idx): """Get the tab indicator color for the given index.""" - return self.tabBar().tab_indicator_color(idx) + return self.tab_bar().tab_indicator_color(idx) def set_page_title(self, idx, title): """Set the tab title user data.""" - tabbar = self.tabBar() + tabbar = self.tab_bar() if config.cache['tabs.tooltips']: # always show only plain title in tooltips @@ -115,7 +128,7 @@ class TabWidget(QTabWidget): def page_title(self, idx): """Get the tab title user data.""" - return self.tabBar().page_title(idx) + return self.tab_bar().page_title(idx) def update_tab_title(self, idx, field=None): """Update the tab text for the given tab. @@ -126,7 +139,8 @@ class TabWidget(QTabWidget): is only set if the given field is in the template. """ assert idx != -1 - tab = self.widget(idx) + tab = self._tab_by_idx(idx) + assert tab is not None if tab.data.pinned: fmt = config.cache['tabs.title.format_pinned'] else: @@ -136,29 +150,40 @@ class TabWidget(QTabWidget): (fmt is None or ('{' + field + '}') not in fmt)): return + def right_align(num): + return str(num).rjust(len(str(self.count()))) + + def left_align(num): + return str(num).ljust(len(str(self.count()))) + + bar = self.tab_bar() + cur_idx = bar.currentIndex() + if idx == cur_idx: + rel_idx = left_align(idx + 1) + " " + else: + rel_idx = " " + right_align(abs(idx - cur_idx)) + fields = self.get_tab_fields(idx) fields['current_title'] = fields['current_title'].replace('&', '&&') fields['index'] = idx + 1 - fields['aligned_index'] = str(idx + 1).rjust(len(str(self.count()))) + fields['aligned_index'] = right_align(idx + 1) + fields['relative_index'] = rel_idx title = '' if fmt is None else fmt.format(**fields) - tabbar = self.tabBar() # Only change the tab title if it changes, setting the tab title causes # a size recalculation which is slow. - if tabbar.tabText(idx) != title: - tabbar.setTabText(idx, title) + if bar.tabText(idx) != title: + bar.setTabText(idx, title) def get_tab_fields(self, idx): """Get the tab field data.""" - tab = self.widget(idx) - if tab is None: - log.misc.debug( # type: ignore[unreachable] - "Got None-tab in get_tab_fields!") + tab = self._tab_by_idx(idx) + assert tab is not None page_title = self.page_title(idx) - fields = {} + fields: Dict[str, Any] = {} fields['id'] = tab.tab_id fields['current_title'] = page_title fields['title_sep'] = ' - ' if page_title else '' @@ -193,9 +218,7 @@ class TabWidget(QTabWidget): fields['protocol'] = url.scheme() y = tab.scroller.pos_perc()[1] - if y is None: - scroll_pos = '???' - elif y <= 0: + if y <= 0: scroll_pos = 'top' elif y >= 100: scroll_pos = 'bot' @@ -215,7 +238,7 @@ class TabWidget(QTabWidget): non-visible. To avoid flickering, disable repaint updates while we work. """ - bar = self.tabBar() + bar = self.tab_bar() toggle = (self.count() > 10 and not bar.drag_in_progress and bar.isVisible()) @@ -304,7 +327,8 @@ class TabWidget(QTabWidget): @pyqtSlot(int) def _on_current_changed(self, index): """Emit the tab_index_changed signal if the current tab changed.""" - self.tabBar().on_current_changed() + self.tab_bar().on_current_changed() + self.update_tab_titles() self.tab_index_changed.emit(index, self.count()) @pyqtSlot() @@ -318,16 +342,13 @@ class TabWidget(QTabWidget): Return: The tab URL as QUrl. """ - tab = self.widget(idx) - if tab is None: - url = QUrl() # type: ignore[unreachable] - else: - url = tab.url() + tab = self._tab_by_idx(idx) + url = QUrl() if tab is None else tab.url() # It's possible for url to be invalid, but the caller will handle that. qtutils.ensure_valid(url) return url - def update_tab_favicon(self, tab: QWidget) -> None: + def update_tab_favicon(self, tab: browsertab.AbstractTab) -> None: """Update favicon of the given tab.""" idx = self.indexOf(tab) @@ -339,11 +360,11 @@ class TabWidget(QTabWidget): def setTabIcon(self, idx: int, icon: QIcon) -> None: """Always show tab icons for pinned tabs in some circumstances.""" - tab = cast(Optional[browsertab.AbstractTab], self.widget(idx)) + tab = self._tab_by_idx(idx) if (icon.isNull() and config.cache['tabs.favicons.show'] != 'never' and config.cache['tabs.pinned.shrink'] and - not self.tabBar().vertical and + not self.tab_bar().vertical and tab is not None and tab.data.pinned): icon = self.style().standardIcon(QStyle.SP_FileIcon) super().setTabIcon(idx, icon) @@ -390,19 +411,37 @@ class TabBar(QTabBar): self._auto_hide_timer.timeout.connect(self.maybe_hide) self._on_show_switching_delay_changed() self.setAutoFillBackground(True) - self.drag_in_progress = False + # FIXME:mypy Is it a mypy bug that we need to specify bool here? + # Otherwise, we get "Cannot determine type of "drag_in_progress" in + # TabWidget._toggle_visibility below. + self.drag_in_progress: bool = False stylesheet.set_register(self) self.ensurePolished() config.instance.changed.connect(self._on_config_changed) self._set_icon_size() QTimer.singleShot(0, self.maybe_hide) + self._minimum_tab_size_hint_helper = functools.lru_cache(maxsize=2**9)( + self._minimum_tab_size_hint_helper_uncached + ) + debugcachestats.register(name=f'tab width cache (win_id={win_id})')( + self._minimum_tab_size_hint_helper + ) + self._minimum_tab_height = functools.lru_cache(maxsize=1)( + self._minimum_tab_height_uncached + ) def __repr__(self): return utils.get_repr(self, count=self.count()) + def _tab_widget(self): + """Get the TabWidget we're in.""" + parent = self.parent() + assert isinstance(parent, TabWidget), parent + return parent + def _current_tab(self): """Get the current tab object.""" - return self.parent().currentWidget() + return self._tab_widget().currentWidget() @pyqtSlot(str) def _on_config_changed(self, option: str) -> None: @@ -486,7 +525,6 @@ class TabBar(QTabBar): Args: idx: The tab index to get the title for. - handle_unset: Whether to return an empty string on KeyError. """ try: return self.tab_data(idx, 'page-title') @@ -562,11 +600,9 @@ class TabBar(QTabBar): icon_width, ellipsis, pinned) - @debugcachestats.register(name='tab width cache') - @functools.lru_cache(maxsize=2**9) - def _minimum_tab_size_hint_helper(self, tab_text: str, - icon_width: int, - ellipsis: bool, pinned: bool) -> QSize: + def _minimum_tab_size_hint_helper_uncached(self, tab_text: str, + icon_width: int, + ellipsis: bool, pinned: bool) -> QSize: """Helper function to cache tab results. Config values accessed in here should be added to _on_config_changed to @@ -597,8 +633,7 @@ class TabBar(QTabBar): width = max(min_width, width) return QSize(width, height) - @functools.lru_cache(maxsize=1) - def _minimum_tab_height(self): + def _minimum_tab_height_uncached(self): padding = config.cache['tabs.padding'] return self.fontMetrics().height() + padding.top + padding.bottom @@ -608,7 +643,7 @@ class TabBar(QTabBar): raise IndexError("Tab index ({}) out of range ({})!".format( index, self.count())) - widget = self.parent().widget(index) + widget = self._tab_widget().widget(index) if widget is None: # This could happen when Qt calls tabSizeHint while initializing # tabs. diff --git a/qutebrowser/misc/checkpyver.py b/qutebrowser/misc/checkpyver.py index e9a7a1eef..7d6a524c3 100644 --- a/qutebrowser/misc/checkpyver.py +++ b/qutebrowser/misc/checkpyver.py @@ -43,11 +43,11 @@ except ImportError: # pragma: no cover # to stderr. def check_python_version(): """Check if correct python version is run.""" - if sys.hexversion < 0x03060100: + if sys.hexversion < 0x03070000: # We don't use .format() and print_function here just in case someone # still has < 2.6 installed. version_str = '.'.join(map(str, sys.version_info[:3])) - text = ("At least Python 3.6.1 is required to run qutebrowser, but " + + text = ("At least Python 3.7 is required to run qutebrowser, but " + "it's running with " + version_str + ".\n") if Tk and '--no-err-windows' not in sys.argv: # pragma: no cover root = Tk() diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index 45c52f54c..a79118044 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -100,7 +100,7 @@ class CrashHandler(QObject): # There's no log file, so we can use this to display crashes to # the user on the next start. self._init_crashlogfile() - except OSError: + except (OSError, UnicodeDecodeError): log.init.exception("Error while handling crash log file!") self._init_crashlogfile() @@ -150,6 +150,7 @@ class CrashHandler(QObject): """Start a new logfile and redirect faulthandler to it.""" logname = os.path.join(standarddir.data(), 'crash.log') try: + # pylint: disable=consider-using-with self._crash_log_file = open(logname, 'w', encoding='ascii') except OSError: log.init.exception("Error while opening crash log file!") @@ -244,7 +245,7 @@ class CrashHandler(QObject): if 'pdb-postmortem' in objects.debug_flags: if tb is None: - pdb.set_trace() # noqa: T100 + pdb.set_trace() # noqa: T100 pylint: disable=forgotten-debug-statement else: pdb.post_mortem(tb) @@ -360,8 +361,7 @@ class SignalHandler(QObject): self._notifier = QSocketNotifier(cast(sip.voidptr, read_fd), QSocketNotifier.Read, self) - self._notifier.activated.connect( # type: ignore[attr-defined] - self.handle_signal_wakeup) + self._notifier.activated.connect(self.handle_signal_wakeup) self._orig_wakeup_fd = signal.set_wakeup_fd(write_fd) # pylint: enable=import-error,no-member,useless-suppression else: diff --git a/qutebrowser/misc/debugcachestats.py b/qutebrowser/misc/debugcachestats.py index 2004ad7ab..9090bd0ea 100644 --- a/qutebrowser/misc/debugcachestats.py +++ b/qutebrowser/misc/debugcachestats.py @@ -23,11 +23,15 @@ Because many modules depend on this command, this needs to have as few dependencies as possible to avoid cyclic dependencies. """ -from typing import Any, Callable, List, Optional, Tuple, TypeVar +import weakref +import sys +from typing import Any, Callable, Optional, TypeVar, Mapping +from qutebrowser.utils import log -# The second element of each tuple should be a lru_cache wrapped function -_CACHE_FUNCTIONS: List[Tuple[str, Any]] = [] + +# The callable should be a lru_cache wrapped function +_CACHE_FUNCTIONS: Mapping[str, Any] = weakref.WeakValueDictionary() _T = TypeVar('_T', bound=Callable[..., Any]) @@ -36,13 +40,21 @@ _T = TypeVar('_T', bound=Callable[..., Any]) def register(name: Optional[str] = None) -> Callable[[_T], _T]: """Register a lru_cache wrapped function for debug_cache_stats.""" def wrapper(fn: _T) -> _T: - _CACHE_FUNCTIONS.append((fn.__name__ if name is None else name, fn)) - return fn + fn_name = fn.__name__ if name is None else name + if sys.version_info < (3, 9): + log.misc.vdebug( # type: ignore[attr-defined] + "debugcachestats not supported on python < 3.9, not adding '%s'", + fn_name, + ) + return fn + + else: + _CACHE_FUNCTIONS[fn_name] = fn + return fn return wrapper def debug_cache_stats() -> None: """Print LRU cache stats.""" - from qutebrowser.utils import log - for name, fn in _CACHE_FUNCTIONS: + for name, fn in _CACHE_FUNCTIONS.items(): log.misc.info('{}: {}'.format(name, fn.cache_info())) diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index c4ff0bb85..4b3df4db2 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -19,7 +19,7 @@ """Things which need to be done really early (e.g. before importing Qt). -At this point we can be sure we have all python 3.6.1 features available. +At this point we can be sure we have all python 3.7 features available. """ try: @@ -109,19 +109,25 @@ def init_faulthandler(fileobj=sys.__stderr__): when sys.stderr got replaced, e.g. by "Python Tools for Visual Studio". Args: - fobj: An opened file object to write the traceback to. + fileobj: An opened file object to write the traceback to. """ - if fileobj is None: + try: + faulthandler.enable(fileobj) + except (RuntimeError, AttributeError): # When run with pythonw.exe, sys.__stderr__ can be None: # https://docs.python.org/3/library/sys.html#sys.__stderr__ - # If we'd enable faulthandler in that case, we just get a weird - # exception, so we don't enable faulthandler if we have no stdout. + # + # With PyInstaller, it can be a NullWriter raising AttributeError on + # fileno: https://github.com/pyinstaller/pyinstaller/issues/4481 # # Later when we have our data dir available we re-enable faulthandler # to write to a file so we can display a crash to the user at the next # start. + # + # Note that we don't have any logging initialized yet at this point, so + # this is a silent error. return - faulthandler.enable(fileobj) + if (hasattr(faulthandler, 'register') and hasattr(signal, 'SIGUSR1') and sys.stderr is not None): # If available, we also want a traceback on SIGUSR1. @@ -205,7 +211,6 @@ def _check_modules(modules): for name, text in modules.items(): try: - # pylint: disable=bad-continuation with log.py_warning_filter( category=DeprecationWarning, message=r'invalid escape sequence' @@ -220,7 +225,6 @@ def _check_modules(modules): category=DeprecationWarning, message=r'Creating a LegacyVersion has been deprecated', ): - # pylint: enable=bad-continuation importlib.import_module(name) except ImportError as e: _die(text, e) @@ -231,7 +235,6 @@ def check_libraries(): modules = { 'jinja2': _missing_str("jinja2"), 'yaml': _missing_str("PyYAML"), - 'dataclasses': _missing_str("dataclasses"), 'PyQt5.QtQml': _missing_str("PyQt5.QtQml"), 'PyQt5.QtSql': _missing_str("PyQt5.QtSql"), 'PyQt5.QtOpenGL': _missing_str("PyQt5.QtOpenGL"), diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index d561a7b96..a4bc1d8ab 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -135,7 +135,7 @@ class ExternalEditor(QObject): message.error("Failed to create initial file: {}".format(e)) return - self._remove_file = True + self._remove_file = config.val.editor.remove_file line, column = self._calc_line_and_column(text, caret_position) self._start_editor(line=line, column=column) @@ -207,8 +207,7 @@ class ExternalEditor(QObject): if not ok: log.procs.error("Failed to watch path: {}" .format(self._filename)) - self._watcher.fileChanged.connect( # type: ignore[attr-defined] - self._on_file_changed) + self._watcher.fileChanged.connect(self._on_file_changed) args = [self._sub_placeholder(arg, line, column) for arg in editor[1:]] log.procs.debug("Calling \"{}\" with args {}".format(executable, args)) diff --git a/qutebrowser/misc/elf.py b/qutebrowser/misc/elf.py index bf824880a..8fadbcffd 100644 --- a/qutebrowser/misc/elf.py +++ b/qutebrowser/misc/elf.py @@ -270,7 +270,7 @@ def _find_versions(data: bytes) -> Versions: correctly: https://github.com/python/typeshed/issues/1467 """ match = re.search( - br'QtWebEngine/([0-9.]+) Chrome/([0-9.]+)', + br'\x00QtWebEngine/([0-9.]+) Chrome/([0-9.]+)\x00', data, ) if match is None: diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index e5ccd1b8b..e14169f93 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -22,12 +22,13 @@ import dataclasses import locale import shlex +import shutil from typing import Mapping, Sequence, Dict, Optional from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess, QProcessEnvironment, QByteArray, QUrl, Qt) -from qutebrowser.utils import message, log, utils, usertypes +from qutebrowser.utils import message, log, utils, usertypes, version from qutebrowser.api import cmdutils, apitypes from qutebrowser.completion.models import miscmodels @@ -168,6 +169,7 @@ class GUIProcess(QObject): self._output_messages = output_messages self.outcome = ProcessOutcome(what=what) self.cmd: Optional[str] = None + self.resolved_cmd: Optional[str] = None self.args: Optional[Sequence[str]] = None self.pid: Optional[int] = None @@ -262,18 +264,19 @@ class GUIProcess(QObject): QProcess.Crashed: f"{what.capitalize()} crashed", QProcess.Timedout: f"{what.capitalize()} timed out", QProcess.WriteError: f"Write error for {what}", - QProcess.WriteError: f"Read error for {what}", + QProcess.ReadError: f"Read error for {what}", } error_string = self._proc.errorString() msg = ': '.join([error_descriptions[error], error_string]) # We can't get some kind of error code from Qt... # https://bugreports.qt.io/browse/QTBUG-44769 - # However, it looks like those strings aren't actually translated? - known_errors = ['No such file or directory', 'Permission denied'] - if (': ' in error_string and # pragma: no branch - error_string.split(': ', maxsplit=1)[1] in known_errors): - msg += f'\n(Hint: Make sure {self.cmd!r} exists and is executable)' + # but we pre-resolve the executable in Python, which also checks if it's + # runnable. + if self.resolved_cmd is None: # pragma: no branch + msg += f'\nHint: Make sure {self.cmd!r} exists and is executable' + if version.is_flatpak(): + msg += ' inside the Flatpak container' message.error(msg) @@ -332,10 +335,23 @@ class GUIProcess(QObject): self.outcome.running = True def _pre_start(self, cmd: str, args: Sequence[str]) -> None: - """Prepare starting of a QProcess.""" + """Resolve the given command and prepare starting of a QProcess. + + Doing the resolving in Python here instead of letting Qt do it serves + two purposes: + + - Being able to show a nicer error message without having to parse the + string we get from Qt: https://bugreports.qt.io/browse/QTBUG-44769 + - Not running the file from the current directory on Unix with + Qt < 5.15.? and 6.2.4, as a WORKAROUND for CVE-2022-25255: + https://invent.kde.org/qt/qt/qtbase/-/merge_requests/139 + https://www.qt.io/blog/security-advisory-qprocess + https://lists.qt-project.org/pipermail/announce/2022-February/000333.html + """ if self.outcome.running: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd + self.resolved_cmd = shutil.which(cmd) self.args = args log.procs.debug(f"Executing: {self}") if self.verbose: @@ -345,7 +361,10 @@ class GUIProcess(QObject): """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) - self._proc.start(cmd, args) + self._proc.start( + self.resolved_cmd, # type: ignore[arg-type] + args, + ) self._post_start() self._proc.closeWriteChannel() @@ -354,7 +373,10 @@ class GUIProcess(QObject): log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, self.pid = self._proc.startDetached( - cmd, args, None) # type: ignore[call-arg] + self.resolved_cmd, + args, + None, # workingDirectory + ) # type: ignore[call-arg] if not ok: message.error("Error while spawning {}".format(self.what)) diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 77b9e8f6c..8c5c05046 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -191,8 +191,7 @@ class IPCServer(QObject): self._atime_timer.setTimerType(Qt.VeryCoarseTimer) self._server = QLocalServer(self) - self._server.newConnection.connect( # type: ignore[attr-defined] - self.handle_connection) + self._server.newConnection.connect(self.handle_connection) self._socket = None self._old_socket = None @@ -270,18 +269,16 @@ class IPCServer(QObject): log.ipc.debug("Client connected (socket 0x{:x}).".format(id(socket))) self._socket = socket self._timer.start() - socket.readyRead.connect( # type: ignore[attr-defined] - self.on_ready_read) + socket.readyRead.connect(self.on_ready_read) if socket.canReadLine(): log.ipc.debug("We can read a line immediately.") self.on_ready_read() - socket.error.connect(self.on_error) # type: ignore[attr-defined] - if socket.error() not in [QLocalSocket.UnknownSocketError, - QLocalSocket.PeerClosedError]: + socket.error.connect(self.on_error) + if socket.error() not in [ # type: ignore[operator] + QLocalSocket.UnknownSocketError, QLocalSocket.PeerClosedError]: log.ipc.debug("We got an error immediately.") - self.on_error(socket.error()) - socket.disconnected.connect( # type: ignore[attr-defined] - self.on_disconnected) + self.on_error(socket.error()) # type: ignore[operator] + socket.disconnected.connect(self.on_disconnected) if socket.state() == QLocalSocket.UnconnectedState: log.ipc.debug("Socket was disconnected immediately.") self.on_disconnected() diff --git a/qutebrowser/misc/keyhintwidget.py b/qutebrowser/misc/keyhintwidget.py index 4fcef72e4..93d9af09d 100644 --- a/qutebrowser/misc/keyhintwidget.py +++ b/qutebrowser/misc/keyhintwidget.py @@ -87,6 +87,7 @@ class KeyHintView(QLabel): """Show hints for the given prefix (or hide if prefix is empty). Args: + mode: The key mode to show the keyhints for. prefix: The current partial keystring. """ match = re.fullmatch(r'(\d*)(.*)', prefix) diff --git a/qutebrowser/misc/lineparser.py b/qutebrowser/misc/lineparser.py index 9d35692e9..fee87354f 100644 --- a/qutebrowser/misc/lineparser.py +++ b/qutebrowser/misc/lineparser.py @@ -99,6 +99,7 @@ class BaseLineParser(QObject): self._opened = True try: if self._binary: + # pylint: disable=unspecified-encoding with open(self._configfile, mode + 'b') as f: yield f else: diff --git a/qutebrowser/misc/miscwidgets.py b/qutebrowser/misc/miscwidgets.py index b89288fc7..4354ed2ab 100644 --- a/qutebrowser/misc/miscwidgets.py +++ b/qutebrowser/misc/miscwidgets.py @@ -213,7 +213,7 @@ class _FoldArrow(QWidget): """Paint the arrow. Args: - _paint: The QPaintEvent (unused). + _event: The QPaintEvent (unused). """ opt = QStyleOption() opt.initFrom(self) diff --git a/qutebrowser/misc/msgbox.py b/qutebrowser/misc/msgbox.py index 9d5fbf601..4271c2639 100644 --- a/qutebrowser/misc/msgbox.py +++ b/qutebrowser/misc/msgbox.py @@ -42,6 +42,7 @@ def msgbox(parent, title, text, *, icon, buttons=QMessageBox.Ok, parent: The parent to set for the message box. title: The title to set. text: The text to set. + icon: The QIcon to show in the box. buttons: The buttons to set (QMessageBox::StandardButtons) on_finished: A slot to connect to the 'finished' signal. plain_text: Whether to force plain text (True) or rich text (False). diff --git a/qutebrowser/misc/objects.py b/qutebrowser/misc/objects.py index 00a1ef35d..63a9cb2dd 100644 --- a/qutebrowser/misc/objects.py +++ b/qutebrowser/misc/objects.py @@ -26,7 +26,7 @@ import argparse from typing import TYPE_CHECKING, Any, Dict, Set, Union, cast if TYPE_CHECKING: - from PyQt5.QtWidgets import QApplication + from qutebrowser import app from qutebrowser.utils import usertypes from qutebrowser.commands import command @@ -47,4 +47,4 @@ backend: Union['usertypes.Backend', NoBackend] = NoBackend() commands: Dict[str, 'command.Command'] = {} debug_flags: Set[str] = set() args = cast(argparse.Namespace, None) -qapp = cast('QApplication', None) +qapp = cast('app.Application', None) diff --git a/qutebrowser/misc/quitter.py b/qutebrowser/misc/quitter.py index a51891685..905429989 100644 --- a/qutebrowser/misc/quitter.py +++ b/qutebrowser/misc/quitter.py @@ -194,7 +194,7 @@ class Quitter(QObject): # Open a new process and immediately shutdown the existing one try: args = self._get_restart_args(pages, session, override_args) - subprocess.Popen(args) + subprocess.Popen(args) # pylint: disable=consider-using-with except OSError: log.destroy.exception("Failed to restart") return False diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py index ee22ba14d..1b72734cb 100644 --- a/qutebrowser/misc/savemanager.py +++ b/qutebrowser/misc/savemanager.py @@ -157,6 +157,7 @@ class SaveManager(QObject): """Save a saveable by name. Args: + name: The name of the saveable to save. is_exit: Whether we're currently exiting qutebrowser. explicit: Whether this save operation was triggered explicitly. silent: Don't write information to log. Used to reduce log spam diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index 11af329e0..a28f3a848 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -206,12 +206,11 @@ class SessionManager(QObject): if item.title(): data['title'] = item.title() - else: + elif tab.history.current_idx() == idx: # https://github.com/qutebrowser/qutebrowser/issues/879 - if tab.history.current_idx() == idx: - data['title'] = tab.title() - else: - data['title'] = data['url'] + data['title'] = tab.title() + else: + data['title'] = data['url'] if item.originalUrl() != item.url(): encoded = item.originalUrl().toEncoded() diff --git a/qutebrowser/misc/split.py b/qutebrowser/misc/split.py index 4db91360e..c7d93e76d 100644 --- a/qutebrowser/misc/split.py +++ b/qutebrowser/misc/split.py @@ -128,6 +128,7 @@ def split(s, keep=False): """Split a string via ShellLexer. Args: + s: The string to split. keep: Whether to keep special chars in the split output. """ lexer = ShellLexer(s) diff --git a/qutebrowser/misc/sql.py b/qutebrowser/misc/sql.py index 814eb2bb0..062da39de 100644 --- a/qutebrowser/misc/sql.py +++ b/qutebrowser/misc/sql.py @@ -479,14 +479,11 @@ class SqlTable(QObject): Args: field: Field to use as the key. value: Key value to delete. - - Return: - The number of rows deleted. """ q = self.database.query(f"DELETE FROM {self._name} where {field} = :val") q.run(val=value) if not q.rows_affected(): - raise KeyError('No row with {field} = "{value}"') + raise KeyError(f'No row with {field} = {value!r}') self.changed.emit() def _insert_query(self, values: Mapping[str, Any], replace: bool) -> Query: diff --git a/qutebrowser/misc/throttle.py b/qutebrowser/misc/throttle.py index ac565b68d..16a14d4bd 100644 --- a/qutebrowser/misc/throttle.py +++ b/qutebrowser/misc/throttle.py @@ -85,6 +85,7 @@ class Throttle(QObject): (cur_time_ms - self._last_call_ms)) # Disconnect any existing calls, continue if no connections. try: + # pylint: disable=no-value-for-parameter self._timer.timeout.disconnect() except TypeError: pass diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 7ba45bdc3..14c02864e 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -23,6 +23,7 @@ import functools import os +import sys import traceback from typing import Optional @@ -125,7 +126,9 @@ def debug_all_objects() -> None: @cmdutils.register(debug=True) def debug_cache_stats() -> None: """Print LRU cache stats.""" - debugcachestats.debug_cache_stats() + if sys.version_info < (3, 9): + raise cmdutils.CommandError('debugcachestats not supported on python < 3.9') + debugcachestats.debug_cache_stats() # type: ignore[unreachable] @cmdutils.register(debug=True) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index d0819f832..c576c4a06 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -87,6 +87,11 @@ def get_argparser(): help="Set the base name of the desktop entry for this " "application. Used to set the app_id under Wayland. See " "https://doc.qt.io/qt-5/qguiapplication.html#desktopFileName-prop") + parser.add_argument('--untrusted-args', + action='store_true', + help="Mark all following arguments as untrusted, which " + "enforces that they are URLs/search terms (and not flags or " + "commands)") parser.add_argument('--json-args', help=argparse.SUPPRESS) parser.add_argument('--temp-basedir-restarted', @@ -207,7 +212,27 @@ def _unpack_json_args(args): return argparse.Namespace(**new_args) +def _validate_untrusted_args(argv): + # NOTE: Do not use f-strings here, as this should run with older Python + # versions (so that a proper error can be displayed) + try: + untrusted_idx = argv.index('--untrusted-args') + except ValueError: + return + + rest = argv[untrusted_idx + 1:] + if len(rest) > 1: + sys.exit( + "Found multiple arguments ({}) after --untrusted-args, " + "aborting.".format(' '.join(rest))) + + for arg in rest: + if arg.startswith(('-', ':')): + sys.exit("Found {} after --untrusted-args, aborting.".format(arg)) + + def main(): + _validate_untrusted_args(sys.argv) parser = get_argparser() argv = sys.argv[1:] args = parser.parse_args(argv) diff --git a/qutebrowser/resources.py b/qutebrowser/resources.py deleted file mode 100644 index ebe256809..000000000 --- a/qutebrowser/resources.py +++ /dev/null @@ -1,5474 +0,0 @@ -# -*- coding: utf-8 -*- - -# Resource object code -# -# Created by: The Resource Compiler for PyQt5 (Qt v5.6.0) -# -# WARNING! All changes made in this file will be lost! - -from PyQt5 import QtCore - -qt_resource_data = b"\ -\x00\x00\x22\xfe\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x80\x00\x00\x00\x80\x08\x06\x00\x00\x00\xc3\x3e\x61\xcb\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ -\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x20\x00\x49\x44\ -\x41\x54\x78\x9c\xed\x9d\x77\x7c\x5c\xd5\x95\xf8\xbf\xf7\xcd\x8c\ -\xa4\x51\xef\xdd\x72\xaf\x72\x93\x3b\xc6\x18\x1b\xdb\x31\xc5\x10\ -\x4c\xc0\x21\x64\x03\x84\xb0\xc9\x26\x6c\xd8\x90\x6c\x60\x7f\xd9\ -\x7c\x36\x24\xd9\x92\xfc\xb2\x69\x64\x61\x37\xc1\xa1\x98\xc0\x62\ -\x02\x36\x60\x83\x0d\xb8\x60\x83\xbb\x2d\xc9\xd8\x72\xc1\x45\xb6\ -\xac\x66\xd5\x51\x9d\xd1\x94\x77\xf6\x8f\xc1\x46\xb2\x66\x34\x33\ -\x7a\x33\x92\x9c\xf0\xfd\x7c\xfc\xb1\xf4\xde\xbb\x45\x73\xcf\xdc\ -\x7b\xde\xb9\xe7\x9c\xab\xf8\x0b\x43\x44\xd4\x8b\x1f\x31\x17\x97\ -\xbe\xc8\xa9\x33\xc5\xa9\xab\x31\x5d\xba\xca\xd5\x3d\x12\xef\x11\ -\x15\xed\x11\x2c\xba\x07\xcd\x03\xca\xe5\x41\x69\x80\xc9\x84\x98\ -\x35\x11\x4d\xa1\x6b\x0a\x97\xd5\xac\xda\xe6\x14\xc8\x4e\x93\x26\ -\x15\x08\xe7\x94\xc8\x59\xb7\xc5\x74\x7c\x56\x26\xe5\x4a\x29\x19\ -\xec\xbf\x31\x9c\xa8\xc1\xee\x80\x51\x36\x1e\x91\x89\x8d\x5d\xfa\ -\x7d\x0e\x8f\x5a\xd2\xde\xc5\xf8\x0e\xa7\x8a\x77\xeb\xc6\xff\xae\ -\x09\x19\xc2\x84\xcc\x9e\x63\x2d\xd0\xa6\x90\x23\x22\xea\x80\x86\ -\xec\x12\xdd\xb4\x6b\x46\x81\xaa\x36\xda\xd6\x60\x72\xd5\x09\xc0\ -\x9a\xc3\x12\x87\x47\xff\x76\x47\x97\x76\x77\x7b\x17\x13\xda\x5d\ -\x44\x4b\x84\xbe\x93\x37\x8c\xd1\x49\x8c\xee\xfb\x19\x81\x93\x9a\ -\xb0\x49\xd7\xf4\xcd\x89\x1d\xe6\xf7\xc7\x8e\x55\x5d\x91\xe9\x4d\ -\x64\xb8\x2a\x04\xe0\x95\xdd\x62\x75\x44\xeb\xdf\x6d\x77\x69\xf7\ -\x36\x77\x32\xb6\xcb\x1d\xf9\x7e\x47\x9b\x61\xd9\x58\x1d\xb3\x16\ -\x42\x21\x45\x0b\xf0\x86\xd2\xe5\x15\x4f\xad\xe9\xdd\x59\xb3\x94\ -\x2b\x52\xfd\x0b\x17\x43\x5a\x00\xb6\x9c\x95\xa9\x95\xcd\xf2\xcb\ -\x4a\x9b\xba\xc1\xe9\x21\x94\xa1\x30\xcc\xac\x7c\x21\x3f\xc9\xd0\ -\xd4\x72\x11\x58\xab\x69\xda\x1f\xa6\xe7\xa8\xb2\x30\x75\x2b\xec\ -\x0c\x39\x01\x78\xfc\x71\xd1\x72\x6f\xe4\xb1\x96\x2e\x1e\x6d\xef\ -\x22\x79\x20\x34\x2e\x93\x82\x11\xa9\xc2\xc8\x54\xa1\xa4\x5a\xc3\ -\x66\x87\xcf\x8d\xd5\x89\x36\x87\xab\x05\xd9\x25\xf0\xf3\x19\xb9\ -\xa6\x8d\x43\x4d\x89\x1c\x32\x02\xf0\x78\x99\x44\xe5\x74\xf0\x0b\ -\x5b\x27\x5f\xef\x70\x12\x13\x8e\x3a\x2d\x1a\xc4\x45\x83\x08\xb4\ -\x3b\xc1\xa3\x43\x42\x34\x98\xba\xcd\x25\x69\xb1\xc2\xa8\x34\x21\ -\xd6\x02\x27\xeb\x14\x27\x1b\x14\xf3\x0a\x84\xac\xf8\x88\x8c\x53\ -\xb1\x42\x7e\x32\x3d\xd7\xf4\xe6\x50\x11\x84\xc1\x17\x00\x11\xf5\ -\x3f\x7b\xf5\xff\x6c\xb2\xab\xbf\x77\xb8\x55\x54\xb8\xaa\xcd\x49\ -\x14\x66\xe5\xc9\xe5\xc1\x6e\xeb\x82\x03\x95\x1a\xf3\x86\xe9\xc4\ -\x5e\xd1\x8a\x08\x1c\xae\x55\x9c\x6b\xf2\x7e\x1c\x63\xd3\x84\xac\ -\x04\x21\x35\x16\xb4\x88\x7c\x42\x72\x50\x94\xfc\x70\x66\xae\xe5\ -\x9d\x48\xd4\x1e\x0a\x83\x2a\x00\xcf\xee\x97\x3b\x1a\xec\xac\x6e\ -\xeb\x22\x25\xdc\x75\x27\xc6\x40\x7c\x94\x20\xc0\xc4\x0c\x21\x31\ -\x06\x6a\x5a\x15\x26\x0d\x32\xbb\x7d\xbb\x05\x28\xa9\x52\x54\xd8\ -\x7a\x7f\x14\x23\x53\x84\x69\xb9\x91\xfc\xa2\xaa\x2d\x26\x93\x7a\ -\x64\x5a\xb6\x3a\x1a\xc1\x46\xfa\xee\xc1\x60\x34\xfa\xdb\x0f\xa5\ -\x40\x53\xb2\xb1\xa1\x53\x4d\x89\xd4\x2b\xdc\x25\xac\x16\x58\x34\ -\xca\xff\x7a\x7e\xa2\x5e\x71\xa2\xce\xf7\xc7\x30\x31\x53\x18\x9f\ -\x11\xe1\x0e\x2a\x5c\x4a\xf8\xb5\x5d\xd7\x1e\x9f\x3f\x4c\xd9\x23\ -\xdb\x58\x6f\x06\x54\xb3\x06\x28\xae\x72\x2d\xf5\x08\x27\xeb\x3b\ -\x22\x3f\xf8\x26\x05\xd7\x0c\xf7\x3f\xf8\x3a\xde\xe9\x3f\x3f\x49\ -\xc8\x49\x14\xaf\x7e\xd0\x4d\x16\xba\xdc\x91\xed\x1f\x00\x82\x45\ -\xe0\xd1\x18\x4d\xff\xe8\x50\xa5\xeb\x86\x01\x68\xb1\x07\x61\xd3\ -\x73\x03\xb1\xbd\x5c\x62\x92\xa2\xf4\xc7\x75\xe1\xfb\x6a\x80\x04\ -\x2f\x39\x96\x3e\x0d\x39\x1a\xde\x6f\xf9\xa7\x78\x97\x0c\x9b\x1d\ -\x8a\xab\xb4\x1e\xca\xe2\x00\x30\x46\x29\x6d\x4b\x49\xa5\xe7\x69\ -\x8f\xa6\x3d\x32\x2b\x57\x75\x0e\x44\xa3\x03\xb2\x04\xbc\x50\x2c\ -\xb7\x35\x77\xca\x8b\x0e\x8f\x8a\xb7\x3b\xc1\x23\x5e\xe5\x4a\x8f\ -\xf0\x0c\x60\x36\xc1\x92\xd1\x9f\xcc\x00\x0a\x3a\x9d\xb0\xaf\x42\ -\x63\x6a\x8e\x90\x11\x37\x24\x94\x70\xdf\x28\x8e\x68\x4a\xfb\xd2\ -\x40\xd8\x0f\x22\x2e\x00\xff\x7b\x58\xfe\x4d\x53\xf2\x8f\x29\x56\ -\x89\x2a\x6f\x52\x54\xb5\x28\xa6\x64\x7b\x5f\xbd\x5a\xed\xd0\xee\ -\x52\x34\x77\xc2\xe9\xc6\xc8\x74\xc5\xa2\x81\x28\x88\xb7\x40\xa7\ -\x0b\x32\xe2\x84\xe9\xb9\x82\xc5\x14\x91\xe6\xc2\x89\x5d\x89\x7c\ -\xb3\x28\xdf\xfc\x7c\x24\x1b\x89\xd8\x12\xf0\xc4\x29\x89\xb6\x34\ -\xb2\x2b\x25\x46\x66\x26\xc5\x08\xc7\x2e\x2a\xaa\x5b\xbd\x83\x2c\ -\x78\x25\x2f\xd1\x0a\x66\x93\x60\x56\x8a\xd3\x8d\xc6\xda\x53\x40\ -\x6a\x1c\xa4\xc6\x08\xe7\x6d\x0a\xa7\xc7\x7b\xdd\xa3\x43\x6a\xac\ -\x90\x99\x00\x79\x89\x42\x5c\xd8\x5e\x34\x23\x8e\x55\x94\x7a\xae\ -\xb8\xda\x33\xa5\x28\x47\x7b\x54\x29\xa5\x47\xa2\x91\x88\x7c\xed\ -\x9e\xfa\x40\x52\xba\x90\xb2\x9c\x44\x72\x86\x27\x0b\x3b\xce\x6a\ -\x74\x76\xb3\x8a\x5f\xda\x64\x29\xab\x55\x9c\x32\xf8\xcd\x8f\x36\ -\x7b\x07\x76\x44\x8a\xf7\x55\xef\x50\x95\xe2\x82\x4d\x91\x1e\x07\ -\xa3\x52\x85\xcc\x38\xc1\x1c\xe1\x6f\xbb\xae\x0b\x95\xb5\xf5\x14\ -\xe4\x66\x46\xa4\x7e\x85\x5a\x6f\x73\xaa\x7b\x16\x8f\x54\x8e\xf0\ -\xd7\x1d\x66\x9e\xde\x27\x23\x6d\x76\x4a\xd2\xe3\x24\x69\x5a\xae\ -\x50\x52\xa5\x38\x7f\xc5\x3b\x76\x51\xae\x60\xb5\xc0\x9e\xf3\x8a\ -\x4b\x2b\x71\x76\xbc\x30\x2e\x53\x88\x36\x43\x79\xa3\x0a\xb8\x24\ -\xe4\x26\x7a\x97\x91\x34\x2b\xa8\x6e\x8f\x96\x5d\x54\x24\x44\xc3\ -\xb0\x64\x19\x10\x05\xe7\xe3\xb3\x95\xfc\x6a\xf5\x6b\x4c\x1a\x5b\ -\xc0\xc3\x5f\x5d\x19\xb1\x76\x44\xa9\xed\xae\x68\x75\xfb\xbc\x34\ -\xd5\x1a\xce\x7a\xc3\xba\x04\x3c\xbb\x5f\x66\x5f\x6c\xe7\x03\xb7\ -\x10\x5d\x98\x25\xb8\x75\xa8\x6c\xe9\x3d\x0c\xa5\x35\x9f\x2e\x05\ -\x97\xa8\x6d\x57\x34\x74\x2a\x34\xc5\xe5\xe9\xdb\x1f\x33\xf3\x84\ -\x61\xdd\x76\x09\x3a\x3a\xed\xe8\x22\xb4\xb5\xdb\x49\x46\x70\xb7\ -\x7b\xa8\x75\x5a\x88\x8f\x8b\x21\x3a\x3a\x8a\x28\x73\xf8\x57\xba\ -\xd6\xb6\x0e\xfe\xe7\xa5\xb7\xd8\xb8\x75\x2f\xba\x2e\x4c\x1a\x5b\ -\x10\xf6\x36\xba\xa3\x44\x16\x47\x39\x64\xdb\xe1\x5a\xb9\x79\x5a\ -\xb6\xaa\x0b\x57\xbd\x61\xfb\x64\x5e\x3a\x28\x0b\xce\xb7\xca\x76\ -\xbb\x5b\x99\xf3\x92\xbc\x26\xd8\xe2\x6a\x85\xa7\xdb\x28\x9b\x14\ -\xa4\xc7\x0b\xf1\x51\x90\x1e\xeb\x1d\xe8\x23\xb5\x0a\xf7\x27\xab\ -\x9b\xfb\x8a\x55\x4e\x44\xc7\xd6\xdc\x44\x53\x7d\x2d\x4d\xf5\x17\ -\x69\x6a\xaa\xc7\xde\xd6\xc2\xfa\x2e\x1b\xcd\x2d\x6d\xb4\xb4\x75\ -\xe0\xf1\x04\x5e\x1a\x35\x4d\x11\x67\xb5\x92\x10\x6f\x25\x3b\x23\ -\x95\x9c\xcc\x54\xee\xbe\x75\x11\x23\x87\x65\x87\xfc\x77\xea\xba\ -\xf0\xe6\x96\x3d\xfc\xe1\xa5\xb7\x68\x6d\x1f\x90\x37\xb5\xee\xcc\ -\xf4\x78\xf4\x0f\x4b\x6a\xe4\x73\x45\x39\xea\x5c\x38\x2a\x0c\x8b\ -\x00\xac\x2e\x95\xa2\xf3\xcd\x6c\xb3\xbb\x95\x19\xa0\xae\x4d\xb1\ -\xfd\xac\xa2\xf5\x8a\x15\x2b\x3e\x06\x26\x65\x08\x2d\x5d\x8a\x33\ -\x4d\xd0\xd8\xa9\xe8\x6e\x0c\x72\xd8\x3b\xa9\x3c\x7f\x86\xea\xca\ -\x73\xd4\x5c\x38\x47\x6d\x75\x05\x2e\xa7\xd3\x70\xff\x74\x5d\x68\ -\xeb\xe8\xa4\xad\xa3\x93\xea\x8b\x5e\x6d\xf3\xda\x59\x93\x43\x16\ -\x80\xb2\x53\xe7\xf8\xe5\xd3\xaf\xf1\xf1\xd9\x4a\xc3\x7d\x32\xc0\ -\x58\x11\x7d\xcb\xfe\x3a\x59\x30\x27\x53\xd5\x1a\xad\xcc\xb0\x00\ -\x3c\x75\x40\xc6\xd7\x35\xc9\x5e\x87\x5b\x59\x2e\x5d\x73\xe9\xe0\ -\xf2\xa1\xae\xb4\xd8\x61\xfb\xd9\x9e\xd6\x95\xa6\x86\x8b\x9c\x39\ -\x79\x94\xb3\x27\xcb\xa8\xba\x50\x8e\xae\x47\x44\xd9\x35\x44\x73\ -\x4b\x1b\xff\xfd\xa7\x8d\x6c\x7a\xff\x00\x12\x69\xf3\x65\x30\x08\ -\xa3\xcd\x2e\xfd\x9d\x92\x72\xb9\xbe\x68\xa4\xb2\x19\xa9\xca\x90\ -\x00\xac\xde\x2d\xa9\x4d\x76\xf6\x87\xba\x8b\xe7\xec\x72\x70\xe2\ -\x68\x31\x65\xa5\x07\xa8\xaa\x38\x6b\xa4\x0b\x11\x45\xd7\x85\x77\ -\x3f\x38\xc4\xef\x9e\x7f\x9d\x96\xd6\x8e\xc1\xee\xce\x95\x4c\xd5\ -\xa3\x65\xdd\xa9\x53\x72\x93\x11\x37\xb4\x7e\x0b\xc0\xef\x0f\x8a\ -\xa5\xdd\x2e\x47\xda\x1d\x2a\x31\xd8\x32\xcd\x4d\x0d\xec\xdb\xf9\ -\x2e\x27\x8e\x16\xe3\x76\x0d\x6d\x6f\xa9\x8f\x8e\x9f\xe5\x57\xab\ -\xd7\x71\xfa\x7c\xd5\x60\x77\xc5\x2f\x4a\x64\x71\x6b\x2c\x2f\xbf\ -\x22\x72\xe7\x2a\xa5\x02\xa8\xce\xbe\xe9\xb7\x00\x78\xba\xe4\x40\ -\x93\x5d\xe5\x06\xf3\x6c\x8b\xad\x89\x7d\x3b\xdf\xe3\x68\xc9\xde\ -\x21\x39\xc5\x77\xa7\xa9\xb9\x95\x27\xff\xb4\x81\x77\x77\x1e\x1a\ -\x1a\xd3\x7d\x00\x14\x72\x7b\xeb\x01\xb6\x01\xd7\xf7\xa7\x7c\xbf\ -\x04\x60\xf5\x7e\xcf\x53\x15\x36\x35\x2d\xd0\x73\x2e\x97\x93\x5d\ -\xdb\xde\xa6\x78\xef\x8e\x21\x3f\xf0\x6e\x8f\x87\xd7\x36\x7d\xc8\ -\x1f\x5f\xd9\x4c\x67\x67\xd8\xed\x2d\x91\x45\x58\xf8\xcc\x41\xcf\ -\xbf\x3f\x30\xcb\xf4\x83\x50\x8b\x86\x2c\x00\x2f\x95\xc8\xed\xa7\ -\x1b\xe4\x9b\x81\x9e\x3b\x7f\xe6\x24\xef\x6d\x58\x8b\xad\xd9\xa0\ -\x8d\x77\x00\xb8\x50\x53\xcf\x0f\xfe\xff\x33\x94\x5f\x30\xac\x54\ -\x0f\x0a\x49\x56\x38\x51\xa7\xfd\xd3\x73\x25\xf2\xd6\xfd\x45\x6a\ -\x57\x28\x65\x43\x12\x80\xff\xda\x27\x69\x17\x6c\xf2\xb2\x5b\xfc\ -\xdb\xd8\x74\x8f\x87\x6d\x9b\xd7\x51\x7a\x60\x17\x5c\x05\x53\x28\ -\x40\xf9\x85\x8b\x57\xed\xe0\x03\x64\x27\x0a\x4e\x0f\x2a\xd9\x2a\ -\xab\x0f\x1e\x94\xa9\xa1\xb8\xa3\x87\x24\x00\xca\x23\xdb\x3b\x5c\ -\xca\xef\x0e\xbb\xa3\xb3\x83\x37\xd6\x3e\xcb\x85\x73\xa7\x42\xa9\ -\xf6\x33\x0c\x12\x67\xb9\xec\xd7\x30\x81\x04\xfd\xa7\xc0\x3f\x05\ -\x5b\x36\x68\x97\x87\xe7\x8b\x3d\xdf\xad\xeb\x50\x53\xfc\xdd\x6f\ -\xac\xaf\xe5\x4f\x7f\xf8\xe5\x67\x83\x3f\xf8\x3c\xba\xe5\x63\x59\ -\x11\xec\xc3\x41\x09\xc0\x4b\x07\x25\xbd\xa6\x55\xfb\x99\xbf\x19\ -\xbd\xb9\xb1\x9e\xb5\xcf\xfd\xd7\x55\xb1\xde\xff\xa5\xe3\xf2\xa0\ -\x0e\xd7\xf2\xca\x1f\x3f\x94\x84\x60\x9e\x0f\x4a\x00\x5a\xdc\xb2\ -\xa9\xd3\x89\xc5\xd7\xbd\xb6\x56\x1b\x7f\x5e\xf3\x14\x9d\xed\x6d\ -\xa1\xf4\xf3\x33\x22\xc4\xb1\x3a\x45\x8b\x03\xab\xd3\xc4\x6b\xc1\ -\x3c\x1f\x50\x00\x5e\x2c\x95\xe5\x17\xdb\xd4\x2c\x5f\xf7\xec\x9d\ -\xed\xbc\xf2\xdc\x93\xb4\xda\x9a\x42\xed\xe7\x67\x84\x19\x9b\x03\ -\xf6\x55\x28\xca\x3f\x89\x6d\xb8\xd8\xce\xb2\x67\x8a\x25\xa0\x6d\ -\x20\xa0\x12\xd8\xd8\x2e\x6b\x74\x1f\x5a\xbf\x88\xce\x9b\xaf\x3c\ -\x4b\x73\x63\xd8\x76\x26\x03\x92\x92\x94\x80\x35\x26\x8a\x96\xb6\ -\x4e\x3a\x3a\x07\xdc\x83\x7a\x48\x22\x02\xa5\xd5\xbd\x7d\x2e\x74\ -\x01\x5b\x3b\x2f\x03\x39\x7d\x95\xef\x53\x00\x9e\x2b\xf1\xfc\xbf\ -\xb3\xf5\xca\xa7\x9b\xcb\x9e\x1d\xef\x70\xa1\xfc\x74\xa8\xfd\x0d\ -\x9a\x28\x8b\x85\x59\x53\xc7\x32\xaf\x68\x22\x85\x63\x87\x33\x72\ -\x78\xce\xe5\x7d\xfd\x27\x9e\x5d\xcf\x2b\x6f\xed\x8c\x58\xdb\x57\ -\x13\x67\x9a\x7a\x0f\xfe\x25\x6c\x0e\xb2\xd7\x1c\x92\x47\xee\x9d\ -\xa9\x7e\xed\xaf\xbc\x5f\x01\x10\x11\xf5\xcb\x9d\xf2\xcf\xbe\xee\ -\xd5\xd7\x56\xb3\x77\xe7\x7b\x21\x77\x36\x18\x72\xb3\xd2\xb8\xfb\ -\xd6\x45\x2c\xbb\x6e\x06\x09\x71\xb1\x11\x69\xe3\x2f\x05\xa7\xc7\ -\x1b\xd8\xd2\x17\xf5\x1d\xf2\x63\x20\x74\x01\x78\xa1\x44\x7f\xbc\ -\xb5\x4b\x8b\xeb\x75\x43\x84\x77\x37\xbc\x8c\xee\xe9\xd7\xde\x83\ -\x5f\x92\x12\xe3\xf8\xc6\x3d\x2b\xb8\x65\xf1\x1c\x4c\x03\xec\x90\ -\x7f\xb5\x52\xde\xa4\x70\x07\x18\x86\x16\x87\x4a\x78\x7a\x9f\x7c\ -\xff\x6f\xe7\xaa\x5f\xf8\xba\xef\x57\x00\x1a\xda\xd5\xf7\x7c\x5d\ -\xff\xb8\xac\x98\x9a\xca\xf3\xa1\xf4\x33\x20\x4b\xae\x9d\xce\xf7\ -\x1e\xbc\x93\xc4\x84\xde\xf2\xf6\x19\xfe\xb1\x07\x69\xef\x6b\x76\ -\xf0\x43\xc0\xa7\x00\xf8\xfc\xaa\xbd\x75\xd4\xfd\x25\xab\x85\xb8\ -\x2b\x5d\xa8\x93\xa2\x75\x0e\xec\x78\x2b\xa4\x4e\xf6\x85\xa6\x69\ -\x3c\xfc\xd5\x95\xfc\xf8\x91\xfb\x3e\x1b\xfc\x10\xd1\x05\x9a\xec\ -\xc1\xb9\xbd\xb6\x77\x91\xf8\xdc\x01\xf9\xba\xaf\x7b\x3e\x67\x80\ -\x9c\x14\xed\xde\x9c\x14\x01\x84\x56\x07\xd4\xb4\x29\xa2\x4c\x70\ -\xaa\xac\x98\x9a\xba\xf0\x18\x7b\x2c\x66\x13\xff\xf2\x9d\xaf\xb0\ -\x78\x5e\xc0\x4d\xc5\xcf\xb8\x02\x11\x28\xad\xea\xed\x72\xd7\x17\ -\xad\x5d\xfc\x08\xf8\xc3\x95\xd7\x7b\xcd\x00\x25\x17\x64\x2c\xc8\ -\xf2\x4b\xbf\x27\xc6\xc0\xf8\x0c\x6f\xf6\x8c\x57\xc3\xa4\x79\x6b\ -\x9a\xe2\xc7\xdf\xbd\xff\xb3\xc1\xef\x07\xad\x0e\xd8\x75\x5e\xa3\ -\xc2\x87\xb7\x75\x5f\x34\xda\xc9\x7d\xb1\x58\x16\x5e\x79\xbd\xf7\ -\x0c\xa0\xe9\x0f\xe2\x23\x5e\xe0\x6c\x45\x0d\xc7\x4e\x87\x67\xed\ -\xff\xd6\x57\x6e\x63\xe1\x9c\xc9\x61\xa9\xeb\xaf\x85\xa6\x4e\xaf\ -\xc6\x5f\xdf\xa1\xfa\xb5\xc9\x2a\x02\xad\x4e\x7e\x06\xcc\xef\x7e\ -\xbd\x87\x00\x1c\x3c\x28\x16\x41\xbf\xd7\x57\x05\x9b\xde\x3f\x10\ -\x7a\xab\x3e\x98\x3f\x73\x12\x77\xdf\xba\x28\x2c\x75\x0d\x15\xd2\ -\x92\x13\x99\x57\x34\x29\xa4\x32\x9d\x8e\x2e\x6c\x2d\xed\xb4\x77\ -\xd8\x69\x6d\xef\xc4\x6a\x8d\x22\x3a\xca\x42\x7c\xac\x95\xcc\xb4\ -\x64\x34\xed\xd3\xc9\xd9\xe1\x86\xbd\x15\x5a\xc0\x78\x89\x40\x34\ -\x75\xc8\xdc\xb7\xdf\x96\xe8\x9b\x6f\xfe\xd4\x87\xb0\x87\x00\xa8\ -\x1c\xcf\x8d\xa0\x7c\xfa\x4a\xef\xdc\x77\xc4\x58\xeb\x40\x42\x5c\ -\x2c\x8f\xfe\xdd\x2a\xc3\xf5\x0c\x15\xcc\x26\x13\x77\xde\x7c\x1d\ -\x0f\xdc\xb5\x9c\xd8\x58\xff\x69\x8d\xda\x3a\x3a\x29\x3d\x76\x96\ -\xd2\xb2\xd3\x9c\xa9\xa8\xa1\xbc\xa2\x96\x46\x9b\xff\x00\x9f\xe8\ -\x28\x0b\xa3\x0b\x72\x19\x33\x32\x97\x59\xd3\x26\xa1\xa7\x14\x1a\ -\x1e\x7c\x80\x2e\x8f\xd2\xea\x33\xf5\xef\x00\x3f\xbf\xfc\x37\x74\ -\x7f\x40\x13\xb5\xca\x97\xaf\x47\x45\xd5\x45\xaa\x2e\x36\x18\xee\ -\xc0\x7d\x77\x2e\x23\x3d\x25\xc9\x70\x3d\x43\x81\x19\x85\x63\x78\ -\xe4\xc1\x2f\xf8\x8d\x2d\xa8\x6f\x6a\x61\xfb\xee\x52\xb6\xed\x2e\ -\xe5\xd8\xe9\xf3\xe8\x21\xc4\xc2\x77\x39\x5d\x1c\x3b\x7d\x9e\x63\ -\xa7\xcf\xf3\xe6\x7b\x7b\x88\xb1\xc6\x32\xbe\xb0\x88\x19\x73\x17\ -\x92\x96\x19\x7a\x30\x4b\x77\xda\x5d\xda\x03\xf8\x12\x80\xed\xe5\ -\x12\x23\x4a\xbf\xcd\x57\xa1\xfd\x87\x3f\x36\xd4\x28\x78\xa7\xc9\ -\x95\xcb\xaf\x35\x5c\xcf\x60\x93\x99\x96\xcc\x43\xf7\x7e\x9e\x25\ -\xd7\x4e\xf7\x79\xbf\xf8\xe8\x29\xfe\xfc\xd6\x4e\x76\x1d\x3a\x16\ -\x36\x3f\x48\x87\xbd\x93\xc3\x07\x77\xf1\xd1\xa1\x3d\x14\x4e\x9f\ -\xc3\xa2\xe5\x9f\x27\xc6\xda\x3f\x2b\x69\x73\x27\x63\x5f\xd9\x2e\ -\xf1\xab\x16\xab\x76\xe8\x26\x00\x89\xd1\xee\x45\x88\xe6\xd3\xc5\ -\xbb\xec\xe3\x73\xfd\x6a\xac\x3b\x77\xdd\xb2\x90\xe8\x28\x9f\x3b\ -\xca\x57\x05\x16\xb3\x89\x55\x2b\x16\x71\xdf\x9d\xcb\x88\x8d\xe9\ -\xed\x14\x75\xfc\x74\x05\x4f\xbd\xb0\x81\x92\xb2\xc8\xed\x8f\x88\ -\xe8\x1c\x2d\xd9\x4b\xf9\xe9\x63\xac\xb8\xf3\x5e\x86\x8d\x18\x1b\ -\x72\x1d\x4e\x0f\xaa\x23\x5e\x7f\x88\x4f\x66\x81\xcb\x02\xa0\x44\ -\xbb\xc9\x5f\xa1\xb2\x53\xc6\xb4\x7f\x93\x49\xe3\xc6\x45\x3e\x77\ -\x94\xaf\x0a\x66\x4f\x1b\xc7\x23\x5f\xfb\x82\xcf\xf0\xef\x96\xd6\ -\x0e\x7e\xff\xd2\x46\x36\x6e\xdb\x17\xd2\x34\x6f\x84\x8e\xb6\x56\ -\xfe\xfc\xfc\x53\x2c\x5d\xb1\x8a\xa9\x33\xaf\x09\xb9\xbc\x03\xed\ -\x0b\x5c\x29\x00\x80\x4f\x01\xe8\x74\x74\x51\x53\x67\x6c\xbf\x7f\ -\xce\xd4\xf1\x57\xe5\xda\x9f\x95\x9e\xc2\xb7\xef\xbf\x9d\x45\xf3\ -\xa6\xf6\xba\xa7\xeb\x3a\xaf\xbf\xb7\x87\xd5\xff\xfb\xf6\x60\x04\ -\x89\xa2\xeb\x3a\xef\x6e\x58\x8b\xee\xf1\x30\x7d\xce\x82\x90\xca\ -\xb6\x3b\xe4\xb2\x6b\x9f\x19\xa0\xf4\x82\xe4\xe9\xe8\x3e\xe7\x93\ -\xca\xea\x7a\xc3\x01\x12\x73\x8b\x26\x1a\x2a\x3f\xd0\x98\x4d\x26\ -\x56\x2e\xbf\x96\xbf\xbd\xe7\x66\xbf\xd3\xfd\xaf\x57\xaf\x0b\x9b\ -\x5d\xa4\xdf\x88\xb0\xf5\xed\x57\x49\x48\x4a\x66\xf4\xf8\xe0\xed\ -\x2a\xed\x4e\x15\xf3\x52\xa9\x14\xde\x33\x5d\x95\x99\x01\x3c\x9a\ -\x67\x81\xf2\x93\x4e\xe1\x42\x8d\x71\xed\x7f\xe6\x94\xd0\xd7\xaa\ -\xc1\xe2\x9a\xa2\x89\xfc\xc3\xd7\xee\x20\x3f\x3b\xbd\xd7\xbd\xa6\ -\xe6\x56\x9e\xfa\xd3\x06\xde\x19\x42\x51\x43\x22\xc2\x5b\xeb\xfe\ -\xc4\xfd\xdf\x7a\x8c\xc4\xa4\xe0\xf2\x6d\x8a\x80\xc3\xcd\x57\x81\ -\x7f\x34\x03\x28\xd4\x7c\x5f\x0f\x7a\x04\x4e\x56\xb5\x18\xea\x60\ -\x62\x7c\x2c\x23\xf2\xb3\x0c\xd5\x31\x10\xe4\x66\xa5\xf1\xf0\xfd\ -\x2b\x59\x30\xbb\xb0\xd7\xbd\xa1\x1e\x35\xe4\x74\xd8\xd9\xbc\xfe\ -\x25\x56\xdd\xf7\xad\x9e\xe9\x52\xfa\xa0\xcb\x25\x8b\xe0\xb2\x0e\ -\x20\xb3\x7d\x65\x8b\xd9\x57\xa1\x38\x5d\xd3\x6e\xa8\x73\x05\xb9\ -\x99\xa8\x20\x3b\x35\x58\x4c\x1e\x37\x9c\x17\x7e\xfd\x98\xcf\xb7\ -\x94\xe2\xb2\xd3\xfc\x7a\xf5\x6b\x43\x3e\x70\xa4\xa2\xfc\x63\x4e\ -\x96\x95\x32\x7e\x72\x51\x50\xcf\x3b\xdc\x6a\x34\x80\x59\x44\xb4\ -\x92\x6a\xbd\x97\xbf\x7f\x6d\xbb\xa2\xae\x5d\x61\xef\x34\x16\x16\ -\x9d\x9f\xd3\x7b\x2a\x1d\x6a\xa4\x26\xf7\xf6\xa0\xae\x6b\xb4\xf1\ -\xe4\x9a\x37\xd8\xba\xab\x74\x10\x7a\xd4\x3f\x3e\xd8\xb2\x81\xb1\ -\x13\xa7\xa2\x99\x02\x67\xc5\xea\x70\x92\x24\x22\xca\x5c\x5c\xc9\ -\x28\xa5\x11\xdf\xfd\xa6\x47\xf7\x66\xf0\x02\x0c\x87\x71\xe7\x66\ -\x0d\x7d\x01\xe8\x8e\xcb\xed\x61\xed\x86\xf7\x79\xfe\xb5\x77\xb1\ -\x3b\x8c\x67\x27\x19\x48\x6c\xcd\x8d\x9c\x2c\x2b\x61\xe2\xd4\xc0\ -\xaf\xdc\x4e\x0f\xea\xc5\x8f\x98\x6b\x46\xf3\x4c\x04\x45\x5b\x17\ -\xd8\xec\x8a\x76\x27\x54\xb7\x7a\x7f\x07\xd0\xdd\xc6\x12\xe6\xc6\ -\xf7\x61\x23\x1f\x2c\x8a\x0a\xc7\x30\x61\x54\x7e\xaf\xeb\xfb\x4b\ -\x4f\xf2\x9b\x67\xd6\x51\x51\x3d\x70\x9e\xce\xe1\xe6\xd0\x9e\x1d\ -\x41\x09\x00\x80\xcb\xa5\x2f\x33\x97\x56\xab\x7b\xaa\x5b\x34\x5c\ -\x7e\xac\x96\x1e\xdd\x98\x00\xc4\xf8\x78\x8d\x1a\x2c\xfc\x99\x71\ -\x6b\xea\x9a\xf8\xdd\x73\xeb\xd9\xb9\x7f\xd0\xb2\xb6\x87\x8d\xda\ -\xea\x0a\x1a\x2e\x56\x93\x9e\x15\x38\x75\x83\xee\x61\xa2\xd9\xad\ -\x6b\xa3\xfd\x0d\x3e\x80\xa6\x19\xcb\xb2\x68\x8d\x1e\xfc\xd4\x9c\ -\x51\x66\x33\x77\x7f\x7e\x11\x5f\x59\xb9\x0c\x6b\xcc\xa7\xfd\xe9\ -\x72\xba\x78\xf1\xf5\x6d\xbc\xf8\xfa\x56\xba\x9c\x91\xcb\x58\x12\ -\x17\x6b\x65\x46\xe1\x68\x46\x0f\xcf\x25\x2b\x3d\x85\xb8\x38\x2b\ -\x1d\x1d\x76\x6a\xea\x1b\x39\x72\xe2\x1c\x87\x8f\x9f\x09\xab\x15\ -\xf1\xf8\x91\x62\xae\x0b\x42\x00\x5c\xa2\x46\x9a\x9d\x6e\xc9\xee\ -\x2b\x5f\x64\xf7\x7d\xe9\xfe\x30\xd8\x89\x21\xe6\xcf\x9c\xc4\xc3\ -\x5f\x5d\xd9\xeb\xbd\xfe\xc3\x03\x65\x3c\xf1\xdc\xfa\xcb\x59\xc3\ -\xc2\x4d\x6c\x4c\x34\x8b\xae\x99\xc6\xe2\xf9\xd3\x99\x35\x65\x1c\ -\x96\x3e\xd2\x95\x56\x5f\x6c\xe4\xf7\x2f\x6d\x0c\x9b\xc2\x79\xea\ -\xf8\x47\x5c\xb7\x34\x70\x7c\xa8\x47\x57\xd9\xe6\xf4\x38\x1c\xcd\ -\x0e\xfc\xba\x17\x2b\x83\x33\x80\xbd\x6b\x70\x14\xa9\xfc\xec\x74\ -\x1e\x7e\x60\x25\xf3\x67\xf4\x74\xd4\xb8\x50\x53\xcf\x6f\x9f\x5d\ -\xcf\xde\xe2\xe3\x11\x69\x37\x36\x26\x9a\x95\x37\x2e\xe0\x4b\xb7\ -\x2d\x22\x39\x31\x3e\x70\x01\xbc\x36\x88\x1f\x3f\x72\x1f\xb3\xa7\ -\x4e\xe0\xe7\xbf\x5f\x8b\x18\x9c\x0d\x9a\x1a\x2e\xd2\xd6\x62\x23\ -\x21\x29\xb9\xcf\xe7\x9c\x3a\x49\xe6\xf1\x19\xe2\xcc\x4a\x10\x76\ -\x95\xfb\xd6\x03\x2c\x16\x63\x53\xb8\xdd\x31\xb0\xe7\x28\x5a\x63\ -\xa2\xb8\xf7\x8e\x65\x7c\xf1\xb6\x45\x3d\x32\x84\xda\x1d\x4e\xd6\ -\xac\x7b\x8f\xb5\x6f\xbe\x8f\xd3\xa0\x62\xeb\x0b\x4d\x53\xac\xb8\ -\x61\x2e\x5f\xbf\xe7\x96\xa0\x07\xfe\x4a\x96\x5e\x3f\x97\x92\x4a\ -\x37\xef\x6c\x78\xd5\x70\x7f\x2a\xce\x9e\xa4\xb0\x68\x6e\x9f\xcf\ -\x38\x3d\x58\xcd\x28\x52\x93\x63\x60\x6e\x81\xb0\xfb\xbc\xea\x95\ -\xc3\x3f\x26\xd6\x58\x74\x4e\x93\x6d\xe0\xa2\x86\x97\x5e\x5b\xc4\ -\xb7\xee\xbd\x8d\xcc\xb4\x9e\x92\xbf\x6d\xcf\x61\xfe\xeb\xf9\xd7\ -\xa9\x6b\x30\x94\x52\xcf\x2f\x93\xc6\x0c\xe7\x91\x07\xef\x60\xe2\ -\x18\x63\xe9\x62\x6b\x5b\x15\x53\x66\x2e\xe0\xd4\x89\x63\x9c\x3d\ -\x75\xcc\x50\x5d\x35\xd5\x15\x01\x05\x40\x74\xcc\x66\x20\x16\x20\ -\x3d\x4e\x58\x30\x52\x38\x5a\xab\xd1\x62\xe7\x72\x8a\x57\x6b\x3f\ -\x1d\x0f\x2e\x71\xa1\x26\xf2\xaf\x54\x05\x79\x59\x7c\xe7\x81\x95\ -\xcc\x99\x36\xbe\xc7\xf5\x8a\xea\x3a\x7e\xfb\xec\x7a\xf6\x95\x9c\ -\x88\x48\xbb\x89\xf1\xb1\xdc\x7f\xd7\x72\xee\xbc\x69\x81\x61\x5d\ -\x09\xbc\x87\x59\x29\xa5\xb1\xf8\xa6\x95\x94\x9f\x3e\x6e\x68\xbf\ -\xe1\x62\xf5\x85\x80\xcf\x78\x74\x34\x33\xc2\xe5\x39\x3e\xd5\x0a\ -\x0b\x47\xea\x78\xc4\x9b\xee\xb5\xb1\x13\xce\x25\xf7\x6f\x3a\xbb\ -\x44\x45\x55\xbd\xa1\xf2\x7d\x91\x10\x17\xcb\x83\x77\xdf\xc4\xed\ -\x9f\x9b\xdf\x23\x9c\xac\xb3\xd3\xc1\x33\x7f\x7e\x87\x57\xdf\xfe\ -\x00\x77\x98\x43\xd8\xc0\xab\x18\xaf\x5c\x3e\x9f\x07\xef\xbe\x29\ -\xac\xf1\x8b\xd6\x28\xef\x49\x2a\x29\x69\x99\x8c\x18\x33\x91\x72\ -\x03\xb3\x40\xc3\xc5\x1a\x44\xa4\x4f\x33\xbc\x0e\xca\x0c\xf4\x5a\ -\xe4\x4d\xca\x7b\xee\x5e\x4e\x22\xb8\x0a\x53\x30\xb2\x22\x5d\xa8\ -\xa9\xa7\xc9\xd6\xe6\xd3\xdc\xda\x6f\x94\xe2\xb6\xa5\xf3\x7a\xad\ -\xb7\x22\xc2\x7b\x1f\x14\xf3\xe4\x9a\x37\xfb\x74\xba\x34\xc2\xd4\ -\x89\xa3\xf8\xee\x83\x77\x30\x66\x78\x5e\x8f\xeb\x9d\x8e\x2e\x9f\ -\x5b\xc7\xa1\xa0\x8b\xf7\x20\x2b\x80\x09\x93\x8b\x0c\x09\x80\xcb\ -\xe5\xa4\xa3\xad\x95\xf8\x44\xff\x7e\x18\x1e\x8f\x57\x00\xfa\x24\ -\x27\x33\xb5\xdf\x9d\x00\xef\xa0\x1c\x38\x7c\x92\xe5\xd7\xfb\xb6\ -\x4e\x9d\x6b\x56\xd8\x5d\xde\x24\x47\x3a\xde\x17\xd2\x40\x5b\x47\ -\x0f\xde\x7d\x53\xaf\x0f\xfb\xf4\xf9\x2a\x7e\xb5\x7a\x1d\x1f\x1d\ -\x8f\x4c\xea\xd9\xb4\xe4\x44\x1e\xba\xf7\x36\x96\x5d\x37\xa3\xc7\ -\xb7\xca\xd6\xda\xce\x1f\x5e\x7a\x8b\x98\x98\x68\x1e\xbe\xff\x76\ -\x43\x6d\x98\x14\x8c\x4b\x17\x4e\xd6\x2b\x46\x8c\x9e\xe0\xdd\xd9\ -\x33\xb0\x0c\xd8\x6c\x0d\x7d\x0a\x00\x78\x77\x03\x9d\x80\xd5\xdf\ -\x03\x99\x69\xc9\x44\x99\xcd\x86\x34\xe7\xad\xbb\x4a\x7a\x08\x80\ -\xd3\x03\xcd\x9d\xde\x63\x62\xea\x3b\x14\xb3\xf3\x85\xd6\x2e\xaf\ -\xef\xbb\x49\x11\xf0\x50\xa7\xee\x83\xdf\xd6\xd1\xc9\xea\x97\x37\ -\xb1\xfe\x9d\xdd\x11\xb1\x39\xf8\x73\xfd\xf6\x78\x74\x5e\x7f\x77\ -\x37\xab\x5f\xde\x44\x5b\x47\x27\xab\x6e\xe9\x15\x74\xd3\x2f\x26\ -\x66\x0a\x9a\x82\x13\x24\x92\x9a\x96\x41\x53\x43\xff\x75\xa8\xd6\ -\xe6\x26\x28\x18\xed\xf7\xbe\xd9\x84\x98\x51\x38\x11\xff\x02\xa0\ -\x69\x1a\xa3\x86\xe7\x72\xe2\x4c\x45\xbf\x3b\xb2\xa7\xe4\x38\xe7\ -\x2e\xd4\x92\x9c\x9e\x4d\x71\xb5\xd6\x2b\xa6\xed\x70\x8d\xc2\xad\ -\x7f\xfa\x06\xb2\xfb\xbc\xa2\x30\x0b\xb2\x13\x04\xb7\x07\xe2\xa2\ -\xb8\x7c\xc8\x53\x5d\xbb\xa2\xc3\x05\xe9\xb1\x3a\xbb\xf6\x1e\xe2\ -\xc9\x35\x6f\xd2\xdc\x12\x99\x37\x8d\xa2\xc2\xd1\x3c\xf2\xb5\x2f\ -\x30\xaa\xa0\x67\x92\x8d\xd2\x63\x67\xf8\xf5\x1f\xd7\x71\xe6\x7c\ -\x75\x44\xda\x1d\x9f\x21\xc4\x46\xc1\x3a\x83\x02\xd0\xd9\xd1\xf7\ -\x56\xbe\x02\x31\x23\x74\x00\x7d\xce\x13\xe3\x46\xe5\x19\x12\x00\ -\x11\xe1\xc9\x17\x36\x70\xd3\xaa\x6f\xd0\x7a\x85\x59\x20\xca\x04\ -\xc9\x56\xa1\xae\x5d\x75\x7b\x1e\x8e\xd6\x2a\x8e\x7e\xb2\x23\x19\ -\x65\xc2\x7b\xca\x98\xc3\xbb\x51\x55\x5b\x55\xc1\xb6\xb7\x5f\xa5\ -\x3a\xcc\x61\xea\x97\xc8\x4c\x4f\xe6\xef\xef\xbb\x9d\x1b\xae\xe9\ -\x19\xbb\x58\xd7\x68\xe3\xa9\x35\x6f\xb2\x65\x57\x49\x44\xda\xed\ -\xce\xb0\x24\x61\xf2\xa8\x0c\xce\x9c\xec\x7f\x1d\x0e\x47\xdf\xbe\ -\x8a\x9a\x86\x6e\x16\xa1\x49\x29\xfa\x34\x1c\x4f\x1a\x33\x9c\x37\ -\xdf\xdb\xd3\xff\x9e\x00\x7b\x8a\x8f\x11\x97\xfb\x21\xd3\x67\xf7\ -\x74\x60\x8c\x36\x43\x8a\x15\xea\xfa\x10\x56\xa7\x07\x4e\xd4\x29\ -\xec\x9d\xed\x7c\xb0\x65\x23\x47\x8a\xf7\x46\xc4\x25\xcb\xdf\x9e\ -\x81\xd3\xed\x66\xed\x9b\xef\xb3\x66\xdd\x7b\x03\xba\x45\x9c\x97\ -\x6e\x4c\x71\x76\xd8\x03\x08\x80\xc2\x6d\x56\x9a\x34\xd0\x47\xea\ -\x57\x08\x9f\x4f\xdf\xd6\xb7\x5e\xc5\xa4\x99\x98\xd2\xcd\x95\xb9\ -\xad\x0b\x4e\x06\x48\x73\xd2\xd9\xd1\xc6\x81\x5d\xdb\x39\x7c\xe0\ -\x43\x9c\xce\xc8\x58\x16\xaf\x29\x9a\xc8\xc3\x0f\xac\x64\x58\x4e\ -\x46\x8f\xeb\xbb\x8b\x8f\xf1\xc4\x33\xeb\xa9\xac\x35\xee\x1b\x19\ -\x2a\x56\x83\x6f\x15\x81\x3e\x2b\xb3\x49\xec\x66\xd0\x1a\x7a\x1e\ -\xdf\xd4\x9b\x9c\xcc\x54\xf2\xb3\xd3\x0d\x7f\x08\x22\xc2\x3b\x6f\ -\xbe\xcc\xb9\x33\x27\x58\xb0\xf4\x56\x52\x52\xfd\x3b\x8b\x78\x3c\ -\x6e\xce\x9d\x3e\xc1\xc7\xc7\x0e\xf3\x71\x59\x29\x2e\x57\x64\xbe\ -\x79\xfe\x7c\x01\x2b\x6b\x1b\x78\xe2\xd9\xf5\xec\x3e\x64\xcc\x22\ -\x67\x04\xa3\x02\xa0\x07\xc8\x1f\x13\x63\x52\x2d\x66\x90\xa0\x16\ -\xf7\x85\x73\xa7\xf2\xd2\x1b\xdb\x0c\x75\xe8\x12\x27\xcb\x4a\x39\ -\x79\xec\x30\xf9\x05\x23\xc9\x1d\x36\x8a\xe4\xd4\x34\x2c\x51\xd1\ -\x74\x39\xec\xb4\xb5\xda\x68\xb8\x58\xc3\x85\x73\xa7\x71\x76\x45\ -\xce\x01\x33\x3a\xca\xc2\xdf\xac\x5c\xc2\x97\x6f\xbf\x81\x28\xcb\ -\xa7\xbe\x80\x76\x87\x93\x17\xd6\xbf\xc7\xcb\x6f\x44\x66\xcf\x20\ -\x14\x4c\x86\x77\x62\xfb\x16\x00\xb3\x46\x8d\x19\xe1\x5c\x30\x95\ -\x2d\x5d\x50\x14\x36\x01\x00\x40\x84\xca\xf3\x67\xa9\x3c\x3f\xf0\ -\x47\xc6\x2c\x9c\x3b\x85\x6f\xdf\x77\x7b\x2f\x1b\xc7\xd6\x5d\xa5\ -\x3c\xb9\xe6\x0d\xea\x1a\x23\xb3\x67\x10\x2a\x46\x37\xd2\x02\x0a\ -\x80\xd2\xcf\x99\x45\xa4\x3c\x18\xaf\xdd\x71\x23\xf3\x19\x37\x32\ -\x9f\x8f\xcb\x07\xf5\xc4\x2c\x43\x14\xe4\x66\xf2\x9d\x07\xee\x60\ -\xce\xf4\x9e\x7b\x06\x75\x0d\x36\x7e\xfa\xbb\x17\x23\x1a\xd7\xd7\ -\x1f\x3a\x0d\x0a\x80\xc9\xe4\xdf\xce\x17\x1f\x05\x09\xd1\xec\x36\ -\xeb\x16\xd3\x31\xd3\x95\x07\xf6\xf9\x61\xd5\x8a\xeb\xf9\xd7\xdf\ -\xbd\x68\xa8\x53\x83\x41\x54\x54\x34\xd7\x5c\xff\x39\x96\x2e\x5e\ -\xcc\xc8\x3c\x8d\x2b\x75\x9e\x13\x67\x2b\x87\xdc\xe0\x03\x74\x18\ -\x8c\x41\x30\x59\x7a\xba\xb9\x9b\x14\x0c\x4f\xf1\x1e\xba\x99\x62\ -\x05\x37\xda\x1b\xe6\x59\x99\x94\x97\x54\xd3\x0a\x04\x3c\xfc\x69\ -\xc9\xb5\x45\xfc\x71\xed\x26\xc3\xb1\x82\x03\xc9\x84\xc9\x33\x58\ -\xb4\xfc\x76\xe2\x13\x93\x68\x71\xc1\xee\xf3\x30\x3a\x15\x26\x66\ -\x09\xa6\xa1\x1d\xae\x60\x38\x27\xc3\x88\x34\x0b\xb3\x87\x09\x36\ -\x3b\xc4\x46\x41\x6e\x82\xf7\x68\x5e\x00\x14\x2d\xb3\x73\xa8\xd4\ -\x94\x52\x02\x12\x94\x37\xa4\xc5\x6c\xe2\xeb\xf7\xdc\x62\xa8\x53\ -\x03\x45\x7a\x66\x0e\x5f\xbc\xff\xdb\xac\xb8\xeb\xbe\x1e\xf6\x70\ -\x11\x38\x51\xeb\x66\xf3\x47\x1d\x01\x93\x2c\x0e\x36\x95\x06\xc3\ -\xf2\x32\x92\xe3\xc9\x4b\x14\x0a\xb3\x84\x91\x29\xdd\x06\x1f\x50\ -\x22\x47\x94\x52\xa2\x01\x88\xa8\xa0\x13\x00\x2d\xbd\xb6\x88\x19\ -\x85\x63\x0c\x75\x2c\x92\x44\xc5\x58\x59\x7c\xe3\x4a\xee\xfd\xe6\ -\xa3\x0c\x1b\xd9\xbb\x9f\xa7\x4f\x1c\xe5\xb9\x27\xff\x83\x63\xa7\ -\xcf\x19\x3e\xb9\x3c\x92\x88\x08\x15\x55\xc6\x7c\x29\xfa\xda\x81\ -\x15\xa5\xf6\xc1\x27\xa1\x61\x1a\xb2\x4b\x50\xff\x10\x4c\xa5\x4a\ -\x29\x7e\xf0\xf7\x5f\xe2\xde\xef\xfd\x62\x68\xc5\xc9\x29\x45\xe1\ -\xb4\xd9\x2c\x5c\x7a\x2b\x71\x09\xbd\x57\xb3\xe6\xa6\x06\xb6\xbf\ -\xfd\x5a\x0f\x4f\x1b\xdb\x10\xea\xfe\x95\x9c\xa9\xa8\xa6\xdd\x60\ -\x46\xf4\xb4\x14\xff\xab\xba\x20\xbb\xe0\x13\x01\x10\xdd\xb4\x0b\ -\x53\xf0\x3b\x69\xd9\x19\xa9\xfc\xf4\x91\xfb\xf8\xfe\x7f\x3c\x3d\ -\xe8\x5e\xbf\x00\x99\xd9\x79\xdc\x70\xf3\x9d\xe4\x0f\x1f\xd5\xeb\ -\x9e\xdb\xe5\x62\xff\xae\x2d\xec\xff\x60\x2b\x6e\x77\x4f\xd7\xef\ -\xba\x76\x45\x63\xc7\xd0\x88\xf2\xbd\x92\x83\x1f\x19\x3f\x7a\xa7\ -\x20\xb7\x0f\x43\x9b\xd9\xb4\x07\x3e\x11\x80\x19\x05\xaa\xba\xb8\ -\xca\xf3\x31\x30\x2e\xd8\xca\xe7\x16\x4d\xe0\x91\xaf\xdd\xc1\xaf\ -\x56\xbf\x36\x68\xa1\xd2\x31\xd6\x58\x16\xdc\x70\x0b\xd3\x66\xcf\ -\x47\xa9\xde\x46\x93\x8f\x8f\x1d\xe6\xfd\xcd\xeb\x69\x6d\x69\xf6\ -\x59\x5e\x04\x0e\x56\x69\x44\x0f\x41\x19\xd8\x57\x6a\xcc\x6b\x59\ -\xd3\x14\x79\xd9\x19\xfe\x6e\x97\x5d\x3a\x78\xfa\xb2\x5a\x20\xc2\ -\x26\xa5\x82\x17\x00\x80\x95\xcb\xaf\xc5\x6c\x32\xf1\x9f\x4f\xff\ -\x39\xa8\x63\xdc\xc3\x85\x52\x8a\x29\x33\xae\xe1\xba\x25\x2b\xb0\ -\xc6\xf5\xce\x31\xdc\xdc\x58\xc7\x96\xb7\x5e\xe5\x7c\x10\x5b\x69\ -\x76\x17\x98\x86\x98\x32\x58\xd7\x68\xe3\xd0\x11\x63\xaf\xa5\xf9\ -\xd9\x19\x7e\x73\x32\x09\x6c\xba\xf4\xf3\xa7\x7a\xa1\xa6\x6f\x42\ -\xb4\xa0\xf4\x80\xee\xdc\xba\x74\x1e\xf9\x39\xe9\xfc\xe8\x37\x2f\ -\xd0\xd4\x1c\x19\x37\xac\xee\x14\x8c\x1c\xc7\xc2\xcf\xdd\x4a\x76\ -\x6e\x6f\x0f\x5c\xa7\xb3\x8b\xbd\x3b\xde\xe5\xd0\x9e\xed\x78\x42\ -\xf0\x05\xec\x6b\x27\x72\x30\xd8\xfc\xfe\x01\xc3\x4b\xeb\xa4\xb1\ -\xfe\x3d\x94\x95\xe8\x9b\x2f\xfd\xfc\x69\x96\xb0\x0e\xf3\xfb\x6d\ -\x71\x7a\x0b\xd2\xb7\x6f\x80\x2f\x8a\x0a\xc7\xf0\xe2\x6f\x1e\xe3\ -\x5f\xff\x67\x23\xbb\xf7\xee\x43\x24\xbc\xb3\x81\xa6\x69\x8c\x9d\ -\x30\x95\xe9\xf3\x16\x32\x6c\xb8\x6f\x0f\x97\x13\x47\x4b\xd8\xf1\ -\xce\xeb\xb4\xb5\x86\x6e\xc6\xad\x6a\x1b\x3a\x6f\x03\x76\x87\x93\ -\x57\x37\x7d\x60\xb8\x9e\xc2\x71\x23\xfc\xdd\xb2\x45\xb7\x98\x2f\ -\x37\x70\x59\x00\xc6\x8e\x55\x5d\xc5\xd5\x9e\x37\x00\x9f\xa9\x62\ -\x03\x11\x15\x1d\xcb\xb5\x37\xdd\xcd\xf8\x59\x8b\x29\xd9\xf7\x01\ -\xc7\x8f\x16\xe3\x30\x90\x5b\x40\xd3\x34\xf2\x86\x8d\x64\x5c\x61\ -\x11\xe3\x26\x4d\xf3\xa9\xd9\x83\xf7\xbc\xc2\x2d\x1b\x5f\xfd\x8b\ -\x39\xaf\xf0\xd5\xb7\x77\x86\x25\x96\xe2\x4a\x73\x77\x37\xd6\x15\ -\x16\xaa\xcb\x5b\xab\x3d\x53\xc5\xea\xf2\x8a\x28\xd5\x2f\x01\xe8\ -\x74\x79\xbd\x5a\x53\xd3\xb3\x58\x72\xcb\x9d\x2c\xbe\xd1\xeb\xdb\ -\x7e\xf6\xd4\x31\xea\x6b\xaa\xa8\xaf\xab\xc6\xe5\xf4\xbf\xa5\x9b\ -\x98\x9c\x4a\x6a\x7a\x26\x19\xd9\x79\x0c\x1b\x3e\x9a\x61\x23\xc6\ -\x60\x89\xf2\xbf\x1d\xea\xec\x72\xb0\xfb\xfd\xcd\x14\xef\xdb\x19\ -\xf6\xd3\x4b\x06\x8b\x8b\x0d\xcd\xbc\xf0\xfa\x56\xc3\xf5\x14\xe4\ -\x65\x91\xe7\x27\x2f\x83\xa6\xf4\xb5\xdd\x7f\xef\x21\x00\xb6\x3c\ -\xd3\x3b\x49\x35\x7a\x0d\xd2\xf7\x49\x53\xbe\x48\x8a\xf1\x7a\xf6\ -\x34\x7f\xf2\xea\xaa\x99\x4c\x8c\x1e\x3f\xf9\x72\xf6\x2a\x11\xa1\ -\xad\xd5\x86\xdb\xe9\xc4\xe5\x72\xe2\xec\xea\x22\xc6\x1a\xeb\xfd\ -\x17\x1b\x1b\x52\x08\xda\xb9\xd3\x27\xd8\xb4\xfe\x45\x3a\xda\x23\ -\xaf\x73\x0c\x14\x22\xc2\xcf\xff\x7b\x6d\x58\x6c\x2b\x8b\xe6\xf9\ -\x3d\xe0\xb5\xd6\x5d\x6d\xde\xde\xfd\x42\x0f\x01\x58\xac\x94\xbb\ -\xb8\xca\xb3\x06\x78\x2c\xd4\x46\x35\x05\x85\x59\x3a\x1f\x9e\xf3\ -\xbd\x87\xad\x94\x0a\x3a\x8b\x55\x20\xca\x4f\x1d\xfb\x8b\x1a\x7c\ -\x80\x35\xeb\xde\x63\xff\x61\x03\x0e\x80\xdd\xb8\x71\xa1\x6f\x17\ -\x7c\xa5\xf8\xe3\x95\x07\x4b\xf7\x1a\x2d\xd1\xb5\xd5\x04\x72\x11\ -\xf2\x81\xcb\x03\xc7\xeb\xff\x7a\x0f\x7b\x0a\x72\x43\xd5\x27\x5b\ -\x77\x95\xb2\xfa\xe5\xcd\x81\x1f\x0c\x82\xa9\x13\x46\x51\x90\xe7\ -\x33\x2b\x9b\xb8\x4d\xda\x33\x57\x5e\xec\x35\x62\x33\x87\xa9\xd3\ -\x28\xb5\xe9\xca\xeb\x81\xf8\xa8\x56\xd1\x68\x2c\x9f\xd4\x55\x4d\ -\x55\x8b\xc2\xd6\x0f\xcb\xed\xd6\x5d\xa5\xfc\xf4\x89\x3f\x85\xcd\ -\x98\xe6\xff\x2c\x06\xb5\x71\x76\x96\xea\xe5\x7d\xe3\xd3\x63\x40\ -\x74\xcf\x2f\x95\xd2\x6e\x0e\xa5\xe1\x01\x4a\x93\x3b\x64\x71\xeb\ -\xb0\xa3\x5c\xa3\xe0\x93\xbd\xf6\xa4\x18\xef\xff\xfe\x10\x11\xd6\ -\x6e\xdc\xc1\x53\x2f\x6c\x08\x9b\x39\xbd\x20\x37\x93\x05\xb3\x7d\ -\x67\x0c\x15\x15\xc2\xb1\x71\x33\xf3\x2d\xdb\x8a\xab\x3c\xc5\xc0\ -\x8c\x60\x1b\xcf\x4d\x04\x83\x39\x25\xaf\x7a\x44\xe0\x7c\xb3\xe2\ -\x7c\xb3\x57\xe7\xb9\x7e\xa4\x4e\xb2\x0f\x21\x68\xb2\xb5\xf1\x9f\ -\x7f\x78\x25\xec\x39\x89\xbe\x7e\xcf\x2d\x68\x5a\x6f\x9b\x86\x52\ -\xb2\x6f\x46\xae\xf2\x69\x5c\xf0\xeb\x33\xa4\x90\x9f\x08\xea\xf5\ -\x60\x1b\xcf\x4e\x10\xd2\xe3\xa0\xa1\x63\xe8\x18\x55\x06\x13\x11\ -\x6f\xdc\xe3\x74\xeb\xa7\x53\xa3\xcb\xed\x61\xc3\xd6\xbd\x3c\xfd\ -\xd2\xdb\xb4\x75\x84\x37\xc1\xf4\xd4\x09\xa3\xb8\x7e\xae\x1f\xed\ -\xdf\xc3\x4f\xfc\x95\xf3\x2b\x00\x45\x79\xe6\x37\x8a\xab\xdd\xfb\ -\x11\x35\x27\x98\x0e\x98\x14\x5c\x3b\x5c\x38\x7a\x11\xce\x0c\xe1\ -\x7d\xf6\x81\xa4\xa9\x53\x01\x42\x6b\x5b\x07\x9b\x77\x1c\x64\xed\ -\xc6\x1d\x5c\x6c\xf0\xbd\x31\x65\x84\x28\xb3\x99\x47\xbf\x71\x97\ -\xef\x50\x70\x91\x03\xd3\xf3\x4d\x7e\x75\xba\x3e\xa3\x83\x05\xf9\ -\x17\x85\x0a\x5a\x3d\x55\x0a\x26\x67\x09\x4d\x9d\xea\xb2\x3d\xe0\ -\xaf\x11\xb7\xcb\x45\x7d\x5d\x35\x25\x95\xe7\xd8\xb4\xf6\x38\x87\ -\x8e\x9c\x8a\xa8\x8b\xf9\xd7\xee\xbe\x91\x11\x7e\x8e\xae\x11\xe4\ -\x9f\xbc\x5e\x5f\xbe\xe9\x53\x00\x66\xe6\x5a\xde\x29\xae\xd6\xdf\ -\x46\x24\x68\x85\x50\x29\x98\x9e\xab\xf3\xfe\x59\xcd\x48\x64\xf3\ -\x55\xc7\x91\xd2\xfd\x9c\x39\x79\x14\x87\xc3\x1e\x30\x24\x2b\x9c\ -\xcc\x99\x3e\x9e\x2f\xdd\x76\x83\xbf\xdb\x6b\x67\xe6\x5b\xfa\xf4\ -\xe5\x0f\xf8\xe2\x2e\x1e\xf5\x0f\x78\x43\xc8\x83\x26\x29\xc6\xeb\ -\x7d\xfa\xd7\x84\xd3\x61\xc7\xd6\xdc\x38\xa0\x83\x3f\x72\x58\x36\ -\x8f\x7f\xe7\x5e\x9f\x8a\x9f\x40\x9b\xa6\x6b\x3e\xcf\x7f\xee\x4e\ -\x40\x01\x98\x39\x4c\x9d\x56\xf0\x9b\x50\x3b\x37\x21\x43\xe8\x23\ -\x35\xde\x67\x18\x24\x33\x3d\x99\x5f\xfe\xf3\x37\x48\x8c\xf7\x9d\ -\xa2\x46\x09\x8f\x4f\x1f\xa6\xaa\x02\xd5\x13\x94\xe9\xce\xae\x6b\ -\x8f\x03\x21\x79\x28\xc4\x98\xbd\xd9\x2e\x3e\x23\xfc\x24\x27\xc6\ -\xf3\x9b\x7f\xf9\x26\x99\xe9\x7e\xf3\x00\x96\xe9\xb5\xda\xef\x82\ -\xa9\x2b\x28\x01\x98\x3f\x4c\xd9\x45\xf4\x6f\x10\xa2\x89\x78\x4c\ -\x9a\x10\x77\xf5\x1e\x14\x36\x24\x19\x96\x93\xc1\x53\xff\xfa\x6d\ -\x9f\x07\x58\x7d\x82\x2e\x4a\xfb\xe6\x95\x36\x7f\x7f\x04\x6d\xbc\ -\x9f\x99\x6f\xd9\x26\xc2\x13\xc1\x3e\x0f\xde\x0d\xa2\x49\x59\x9f\ -\xcd\x02\xe1\x62\xfa\xa4\xd1\xfc\xf7\xbf\x3d\xdc\xd7\xe0\x23\xc2\ -\xcf\x66\xfa\x31\xfa\xf8\x22\xa4\xdd\x9b\x44\xbb\xf6\x18\x70\x38\ -\x94\x32\x79\x49\x42\x5e\xd2\x67\x42\x60\x04\xa5\x14\xab\x6e\x59\ -\xc8\x6f\x7f\xf4\xad\x00\x59\x48\x65\xaf\xd4\x6a\x8f\x87\x52\x77\ -\xc0\x2c\x61\xdd\x19\x3b\x56\x75\x95\xd6\xc8\x97\x75\x5d\xdf\x07\ -\xf4\xf6\xc6\xf4\xc3\xf4\x5c\xa1\xb1\x43\xe1\x18\xdc\x68\xeb\xcb\ -\xa4\x65\x64\x93\x9e\x61\xec\x08\xd6\x81\x62\x58\x4e\x06\x8f\xfe\ -\xdd\x2a\x8a\x02\x07\xe3\x34\x98\xc4\x74\xd7\x8c\x20\xa7\xfe\x4b\ -\x84\x24\x00\x00\xd3\x73\x54\xd9\xa1\x6a\xf7\x57\x94\xa8\xd7\x08\ -\x9c\xd1\x0d\x00\x8b\x06\x63\xd3\x85\x23\xb5\x83\x6b\x21\x8c\x8a\ -\x8e\x61\xfe\xe2\x9b\x98\x31\xe7\xba\xa0\x8e\x55\x19\x4c\xcc\x26\ -\x13\x5f\xbc\x75\x11\x0f\xac\x5a\x1e\xcc\x89\xab\x02\xf2\xb5\x69\ -\xf9\x2a\xe4\xd0\xed\x90\x05\x00\x60\x66\xae\x79\x7d\x71\xb5\xe7\ -\x57\x08\x01\xdf\x33\x2f\x91\x95\x20\x94\xd5\x2a\x06\x2b\x8c\x64\ -\xf4\xb8\x42\x96\xae\x58\x15\x30\x83\xf6\x60\x63\x36\x99\x58\xb2\ -\xa0\x88\xaf\xde\xb5\xdc\xe7\xd1\x75\x3e\x11\x7e\x36\x23\xdf\xfc\ -\x66\xbf\xda\xeb\x4f\x21\x80\xa2\x1c\xed\xd1\x92\x6a\x3d\x1f\xf8\ -\x62\x30\xcf\xc7\x47\x79\xf3\xff\x1d\xae\x55\x03\x6a\x21\xcc\xc8\ -\xca\x63\xc9\x2d\xbe\xa3\x86\x86\x12\xb1\x31\xd1\x2c\xbb\x6e\x26\ -\x7f\xb3\x72\x49\xa8\xc9\x39\x5f\x2e\xca\xd3\x7e\xd8\xdf\x76\xfb\ -\x2d\x00\x4a\x29\xfd\xe0\x41\xf9\x8a\x96\x23\xc9\x20\xcb\x83\x29\ -\x33\x22\x55\x88\x8f\x86\xd2\x6a\xef\xd9\x44\x91\x24\x3a\xc6\xca\ -\xfc\xc5\x37\x51\x34\xe7\xba\xb0\x24\x72\x8e\x14\xe3\x47\x0f\xe3\ -\xf3\x4b\xaf\x61\xe9\x82\x19\xc4\x5a\x43\xcd\x09\xa4\xb6\x25\x74\ -\xaa\xfb\x95\x52\xfd\x9e\x58\xfb\x2d\x00\x00\xb3\x66\x29\xd7\x87\ -\xf5\x72\x57\xac\x53\xb6\x03\x33\x83\x29\x93\x1e\x27\xdc\x30\x46\ -\x28\x6f\x52\x9c\xac\x57\x38\xc3\xed\xd0\xab\x14\x93\xa7\xcf\x61\ -\xe1\xb2\x5b\x89\x8d\x0b\x63\x7e\xe2\x30\x61\x31\x9b\x98\x3a\x71\ -\x14\xd7\xce\x2c\x64\xfe\xac\xc2\xe0\xa7\xf9\x5e\xc8\xc1\x68\x8b\ -\xf6\xf9\xb1\x63\x95\xa1\x34\x22\x86\x04\x00\x60\x41\x86\x6a\x3b\ -\x58\x2d\x37\x2a\xd1\x3f\x54\xe0\xd7\x19\xbd\x3b\x9a\x82\xd1\x69\ -\xc2\x88\x14\xa1\xaa\x4d\x71\xba\x5e\xf5\x4a\x20\xd9\x1f\xb2\x72\ -\xf2\x59\xb2\xe2\x2e\x72\xf3\x47\x18\xaf\x2c\x4c\x58\xad\xd1\x4c\ -\x1a\x53\xc0\xe4\x71\x23\x99\x3c\x61\x04\xd3\xc6\x8f\xec\x91\x72\ -\xb6\x5f\x28\xce\x58\x4c\xa6\x15\x85\x99\xca\x70\x4c\x93\x61\x01\ -\x00\x98\x95\xab\x1a\x4a\x6a\xe4\x46\xd1\xf5\x77\x81\xa0\x93\x0a\ -\x9a\x34\x28\x48\x12\xf2\x13\x85\x13\xf5\x8a\x53\x0d\xfd\xd3\x0f\ -\x62\xac\xb1\x5c\xb7\x64\x05\x53\x67\xcd\xef\xd7\x29\xa5\xa9\x69\ -\x19\x4c\x2e\x9a\x47\xab\xad\x89\x56\x5b\x23\x76\xbb\x1d\x67\x97\ -\x23\xa8\x08\x27\xb3\xc5\x42\x74\xb4\x95\xec\x8c\x24\xb2\xd3\x12\ -\xc9\x4a\x4b\x26\x2f\x27\x83\x11\x79\x59\x0c\xcb\xcd\x20\x3b\x23\ -\xd5\xe7\x66\x8d\x01\x4e\x81\xb6\x6c\x4a\x96\xba\x18\x8e\xca\xc2\ -\xda\xb3\x7d\x95\x92\x66\x51\x9e\x8d\xa0\xe6\xf5\xa7\x7c\x65\x8b\ -\xe2\x50\x55\x60\x21\xd8\xbe\x69\x1d\x87\xf6\xee\xb8\x1c\x24\xba\ -\x70\xe9\x0a\x62\x62\x83\x36\x4b\x04\x8d\xc7\xe3\xc1\xed\xea\xc2\ -\xe1\x70\xe0\x71\xb9\xd0\xcc\x66\x34\xa5\x88\x8e\x89\x41\xd3\xcc\ -\x58\xa2\xbc\xb1\x0c\xf1\x51\xb0\x64\x8c\x1e\xec\xb1\xbd\x06\x90\ -\x83\x26\x93\xe9\x96\x69\xd9\x2a\x6c\xa7\x70\x84\x65\x06\xb8\xc4\ -\xdc\x7c\xd5\x58\x56\x27\xcb\xba\x5c\xf2\x6a\xb0\x8a\x61\x77\xf2\ -\x93\x84\xba\x76\xa8\xb0\x05\xfe\x24\x07\x62\xba\x37\x99\x4c\x98\ -\x4c\xb1\x44\xc7\xf4\x7d\x28\x44\x7a\x9c\x0c\xc0\xe0\xab\x6d\xce\ -\x18\x6d\xe5\xbc\x34\x15\xd6\x80\x88\x88\x74\xbb\xac\x4c\xa2\xba\ -\x92\xf5\xe7\x81\xbb\x43\x2d\x5b\xdb\xa6\xd8\x5b\xd1\x77\xb7\x9a\ -\x1a\xea\x48\x49\xcb\x18\x32\x87\x52\x8f\x48\x11\xa6\xe7\x46\xf0\ -\xdd\x56\xd4\xba\x16\x97\xfa\xf2\xe2\x91\x2a\xec\x39\x4d\xc2\x3a\ -\x03\x5c\xa2\xb0\x50\x39\x45\xe4\xcb\xa5\xd5\x7a\x85\xc0\xf7\x09\ -\x41\xd0\x52\xac\x12\xf0\xf1\xd4\x74\xff\x9b\x21\x03\x8d\x22\xa2\ -\xce\x2f\x82\xf0\xb3\xa2\x3c\xf5\x43\x23\xaf\x7a\x7d\x11\xb1\x17\ -\x64\xa5\x94\x5e\x94\x67\x7a\x0c\xf4\xcf\x01\x41\x2b\x2c\xd1\x66\ -\xaf\x2f\x8d\x61\x08\x64\x00\x00\x02\x6f\x49\x44\x41\x54\xc1\xd5\ -\x42\x4e\x62\xdf\xfe\xff\x06\x68\x50\xba\xac\x98\x91\x6f\xfa\x41\ -\xa4\x06\x1f\x22\x28\x00\x97\x98\x91\x67\xd9\x62\x12\x6d\x96\x42\ -\x3e\x0c\xb6\x4c\xd4\x55\x24\x00\x63\x23\xe1\xf4\xa2\x64\xbf\xd2\ -\xb4\xd9\x45\xc3\xcc\x6f\x87\xbf\xf2\x9e\x0c\x88\x89\x6c\x5a\xbe\ -\xaa\xb4\xe5\x9a\x16\x0b\xfc\x18\x02\x6f\x07\x0c\xf5\x04\x8e\x97\ -\xc8\x88\x0b\xfb\xb7\x5f\x80\x27\xf4\x6a\xd3\x82\xa2\x1c\x75\x2e\ -\xac\x35\xfb\x61\xc0\x3f\xea\xe2\x1a\xb9\x1e\x5d\x7f\x12\x28\xf4\ -\xf7\xcc\x8e\xb3\xda\x90\x76\x2b\x57\x0a\xd2\x63\x85\x19\x79\x82\ -\x35\x5c\x1e\x4f\x8a\x23\x82\xf6\x50\x28\xce\x1c\xe1\x69\x76\x10\ -\xd8\x2e\x62\x4e\xae\xd1\x1f\xd2\x85\x9f\x2a\xe8\x65\xaf\x1d\x8a\ -\x02\x90\x11\x27\x8c\x48\x85\x84\x28\x21\x2e\x3a\xac\xb3\x54\xa7\ -\xc0\x2f\x62\x6c\xda\xbf\x77\xcf\xdc\x31\x50\x0c\xea\x64\x5b\x5c\ -\x21\xb9\x68\xfa\xcf\x50\x7c\xa5\xfb\xf5\x7d\x17\x14\x35\xad\x43\ -\x63\x1d\x48\xb1\x7a\xdd\xda\xfa\x3a\xc5\xac\xbf\x28\xd4\x46\x8f\ -\x52\x0f\xcd\xca\x55\xfd\x3f\x90\xc9\x70\x1f\x86\x00\xc5\x95\xae\ -\x25\x28\xf5\x23\x50\xd7\x01\x1c\xa9\x55\x83\x1e\x5e\x66\x36\x41\ -\x51\x4e\xa4\xdc\xd9\x64\xaf\x88\xfc\x73\xa0\xa0\x8d\x81\x60\x48\ -\x08\xc0\x25\x8a\xab\x65\x21\x22\x3f\x28\xab\x65\xf9\x60\xe7\xf1\ -\xbd\xa6\x40\xc8\x4a\x08\xf3\xe0\x2b\xd9\xaf\x3c\xfc\x78\x20\xb4\ -\xfb\x60\x19\x52\x02\x70\x89\x67\x0f\xca\x3d\x2d\x0e\xfe\xa3\xd9\ -\x4e\xc1\x60\x84\x97\x59\x2d\xb0\x7c\x5c\xd8\x5e\xbd\x05\xd4\x56\ -\x41\x7f\x62\x66\x9e\x79\x43\xb8\x2a\x0d\x17\x43\x52\x00\x2e\xf1\ -\xec\x61\x99\x67\xb7\xf3\xef\xb6\x4e\x59\xe8\x70\xab\x01\x73\xe2\ -\x8b\x31\xc3\x8d\xe3\x0d\x0a\x80\xa2\x06\x61\x8d\xee\xd1\x9e\x9e\ -\x55\xa0\xce\x84\xa7\x67\xe1\x67\x48\x0b\xc0\x25\x5e\x29\x93\x28\ -\x87\x53\xff\x5e\xab\x5d\x7b\xc0\x66\x67\xb4\xd3\x13\xf9\x7e\xdf\ -\x3c\x41\x27\x2a\x74\x91\x6b\x56\xf0\xba\xae\xf4\xb5\xad\x39\xe6\ -\xad\x8b\x95\x1a\x22\x7e\xd0\xfe\xb9\x2a\x04\xa0\x3b\x1b\x0e\x4a\ -\xac\x4d\xe9\x0f\xb5\x3b\xd5\xdd\xad\x0e\x55\xd8\xe9\x8a\x4c\xae\ -\xe7\x25\x63\x74\x12\x82\xf3\xd0\x2a\x43\xb1\x19\xd1\x37\x47\xdb\ -\xcc\x3b\x07\xe3\x55\xce\x08\x57\x9d\x00\x5c\xc9\x9a\xc3\x32\xd9\ -\xe9\xd2\xbf\xea\x74\xa9\xc5\x76\x37\xa3\x3b\x9d\x2a\xc1\x65\x70\ -\x86\x48\xb1\xc2\x75\xa3\xf4\xde\x66\x52\x45\x8b\x12\x39\x82\x52\ -\x07\x74\xe4\xc3\x28\x93\x69\x57\xb8\x1c\x33\x06\x8b\xab\x5e\x00\ -\x7a\x21\xa2\x9e\x29\x61\xa1\x86\x7e\x9d\xcb\xc5\x14\xa7\x68\xa3\ -\x3c\x3a\x99\x2e\xb7\x24\xba\x45\xc5\xb8\xdd\x58\x04\x34\x5d\xc0\ -\xad\x7b\x77\x94\x2d\x1a\xa2\x29\x44\x53\xe8\x9a\x26\xee\x59\xf9\ -\x1c\x4e\x8e\x91\x72\x81\x0b\x4a\x28\x17\x4d\x2f\x57\x98\x8f\xcf\ -\xc8\x55\x91\x39\xac\x78\x10\xf9\x3f\x06\x8e\x97\x48\x80\x7c\xb0\ -\xcc\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x9a\xb3\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x02\x00\x00\x00\x02\x00\x08\x06\x00\x00\x00\xf4\x78\xd4\xfa\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ -\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x20\x00\x49\x44\ -\x41\x54\x78\x9c\xec\xdd\x77\x78\x5c\xe5\x95\x3f\xf0\xef\x7b\x67\ -\xd4\xbb\x2c\xc9\x2a\x96\xdc\x3b\x6e\xb2\xc1\xc6\xa6\x98\xde\xc1\ -\x18\x30\xa1\xf7\x40\x1a\x09\xd9\xdd\xb4\xcd\x66\xbd\xc9\x66\xb3\ -\xbf\x4d\x02\x29\xd4\xd8\xc6\x14\x63\xc0\x60\x63\xc0\x31\xee\x06\ -\xf7\x22\xc9\x72\x91\x9b\x24\xcb\xb2\x7a\xd7\xf4\x76\xef\x3d\xbf\ -\x3f\x06\x83\x8b\xca\x48\x9a\xb9\x77\xca\xf9\x3c\x4f\x9e\x80\x34\ -\xba\xef\x41\x65\xde\x73\xdf\x7b\xde\xf3\x0a\x30\xc6\x82\xda\xda\ -\xb5\x14\x63\xce\xc3\x68\x08\x8c\x54\x14\x75\x98\x42\xc8\x96\x15\ -\x0c\x56\x49\xca\x50\x55\x4a\x57\x48\xa4\xc8\x2a\x25\x79\x54\x11\ -\x07\xc0\x08\xc0\xa8\x10\x19\x05\xc1\xa0\x02\x92\x4a\x30\x00\x80\ -\xa2\x08\x10\x20\x08\x10\xb2\x0a\x10\x01\x42\x00\x51\x12\x40\x00\ -\x24\x40\x35\x18\x08\x00\x60\x14\x82\x84\x80\x4c\x44\x30\x1a\x84\ -\x8b\x54\x22\x49\x12\xee\x28\x03\x39\x8d\x06\x61\x33\x08\x32\x1b\ -\x25\x74\x18\x04\xb5\x19\x0d\x52\xb3\x41\xa8\x8d\x31\x46\xaa\x8f\ -\x8d\x36\x9c\xca\x88\x46\x95\x29\x1f\xcd\xd7\x08\x21\xeb\xf7\x5d\ -\x63\x8c\xf5\x46\xe8\x1d\x00\x63\x91\x6c\x75\x05\xe5\xbb\xad\x98\ -\xe1\x94\xd5\x49\xb2\x8a\xb1\xb2\x2a\x0d\x75\xab\xc8\x71\x2b\x94\ -\xee\xf6\x20\xde\xa3\x8a\x28\x8f\x1a\x5a\x7f\xa7\x42\x00\x31\x06\ -\xe0\x8a\x61\x6a\x7b\x62\x0c\x1a\x20\xa8\x45\xa8\xa2\x96\x04\xea\ -\x08\xa8\x05\xa8\x5a\x22\x43\x8d\x22\xa1\x76\x46\xae\x68\xd5\x3b\ -\x5e\xc6\x22\x55\x48\xbd\xb1\x30\x16\x6a\x56\x10\x19\x94\x43\x98\ -\xe1\xf6\xa8\x73\x5d\x0a\x0a\x3d\xaa\x34\x4a\x56\x28\xc7\xa3\x8a\ -\x54\x97\x8c\x58\x97\x1c\xbe\x7f\x83\x39\xc9\x84\x99\xf9\xd4\xdb\ -\xcb\x1c\x10\xa8\x20\x12\x95\x00\x55\x80\x50\x21\x84\x5a\x29\x24\ -\x63\xc5\xd4\x6c\x9c\x11\x42\xa8\x5a\xc4\xca\x58\x24\x0a\xdb\x37\ -\x1f\xc6\xb4\xf6\xee\x41\x2a\x94\x9d\xea\x0d\x1e\x21\xcd\x74\x7b\ -\x68\xbc\x5b\x15\xb9\x76\x0f\x92\xdc\x61\x3c\xc9\xf7\x44\x08\xe0\ -\xba\x51\x2a\x12\xa3\xfb\x7d\x09\x37\x80\x0a\x08\x51\x46\x44\x47\ -\x25\xa2\x32\x61\x30\x1c\x3d\x99\x8d\xe3\x0b\x84\x50\xfc\x17\x29\ -\x63\x91\x29\x22\xdf\x98\x18\x1b\x88\x37\x8a\x28\x3e\x5a\xc1\x6d\ -\x32\x70\x9b\x4b\xa6\x19\x4e\x19\x05\x0e\x59\x24\x7a\x14\xfe\x7b\ -\xba\xd0\xd0\x54\xc2\xb4\xbc\x5e\x57\x01\xfa\x46\xc0\x03\x42\x39\ -\x08\xc5\x42\x42\xb1\x02\xa9\x18\x40\xc9\x8c\x5c\x61\xf7\xef\x40\ -\x8c\x85\x37\x7e\xc3\x62\xac\x07\x2b\x88\x0c\x9e\xc3\xb8\x49\xf6\ -\xa8\xf3\x1c\x2e\x31\xdb\xae\x60\xb8\xc5\x29\xe2\x15\x3f\xcf\x69\ -\xe1\x4a\x08\xe0\xb2\x21\x84\x9c\xe4\x80\x7f\xc3\x64\x00\x47\x01\ -\xec\x03\x68\x8f\xc1\x60\xd8\x3b\x79\x30\x8e\xf2\x23\x04\xc6\xba\ -\xc7\x09\x00\x63\xe7\x78\xa3\x88\x0a\x62\x49\x7d\xc2\xa1\x48\xd7\ -\x39\x15\x4c\xb0\x3a\x91\xee\xe6\x3b\xfb\x01\x91\x04\x70\xa9\x36\ -\x49\xc0\x85\xcc\x80\xd8\x2f\x04\xed\x21\x41\x3b\xed\x46\xc3\x8e\ -\x2b\x32\x85\x45\xeb\x20\x18\x0b\x56\xfc\xc6\xc6\x22\xda\xf2\xc3\ -\x34\xd8\xe5\x52\x9f\x70\x79\xc4\x3c\xab\x5b\x4c\xb4\xb8\x91\x48\ -\x7c\x77\xef\x77\x02\xc0\x98\x4c\xc2\xf8\x2c\x5d\xbf\xb9\x0a\x80\ -\x13\x82\xb0\x03\xa0\x4d\x8a\x64\xd8\xca\xbb\x10\x58\x24\xe3\x04\ -\x80\x45\x94\x65\xe5\x94\x2c\xcc\xea\x53\x76\x45\xba\xc7\xe2\xa4\ -\xa9\x66\x97\x48\x50\x79\xc2\xd7\xcc\xc4\xc1\x84\xd1\x19\x41\xf3\ -\x0d\x57\x09\x38\x28\x01\x1b\x09\xea\x46\x93\xdb\xb8\xe3\x9a\xe1\ -\xc2\xa9\x77\x50\x8c\x69\x85\x13\x00\x16\xf6\xde\x2d\xa6\x05\x0e\ -\x0f\x3d\x66\x77\x8b\x99\x66\x37\x06\xc9\xfc\x54\x58\x37\x71\x51\ -\xc0\x4d\x63\x82\xf6\x07\xe0\x20\x88\xed\x12\x68\xa3\xa2\x4a\xeb\ -\x67\xe4\x8b\xc3\x7a\x07\xc4\x58\x20\x71\x02\xc0\xc2\xce\x3b\x07\ -\x29\x41\x52\xd5\x67\x6c\x2e\xf1\x70\x87\x03\x53\xec\x1e\x61\xd4\ -\x3b\x26\xe6\x65\x94\x80\xdb\xc7\x07\x6d\x02\x70\xa1\x6a\x41\x58\ -\xaf\x0a\x5a\x63\x76\x1b\x36\xf2\xea\x00\x0b\x37\x9c\x00\xb0\xb0\ -\xf0\xce\x41\x1a\xae\xb8\xd5\x9f\xdb\x3c\xd2\x6d\x1d\x0e\xe4\xf1\ -\x96\xbc\xe0\x14\x6d\x00\x6e\x1d\x17\x32\x09\xc0\xb9\x6c\x02\x62\ -\x03\x48\xfd\x2c\x9a\x0c\x9f\x4d\xcc\x17\xed\x7a\x07\xc4\xd8\x40\ -\xf1\x9b\x24\x0b\x59\xcb\x8b\x68\x9c\x83\xf0\x82\xc5\x8d\xbb\x4c\ -\x76\x0c\xe6\xad\x79\xc1\xcf\xc7\xee\x80\xc1\x4e\x01\x68\x8f\x10\ -\xe2\x23\xc5\x2d\xad\x98\x31\x4c\x34\xe8\x1d\x10\x63\xfd\xc1\x09\ -\x00\x0b\x29\xcb\x0e\xd1\x24\x9b\x13\x0b\x2d\x76\xba\xd1\xec\x16\ -\x89\x7a\xc7\xc3\xfa\xe6\xf2\x02\xc2\xe0\xa4\x90\x4f\x00\xce\xa5\ -\x00\xb4\x93\x20\x56\x08\x49\x5a\x51\x98\x23\x5a\xf4\x0e\x88\x31\ -\x5f\x71\x02\xc0\x82\xde\x6b\x3b\x29\xcb\x18\xa5\xfe\xc6\xea\x92\ -\x16\x74\x3a\x91\xc9\xdb\xf4\x42\x53\x4e\x12\x61\x66\x41\x58\xff\ -\xf0\x14\x80\xf6\x90\x10\xef\x38\xa2\xa4\xf7\xb9\xe7\x00\x0b\x76\ -\x9c\x00\xb0\xa0\xf4\x46\x11\xc5\x47\xab\xea\x2f\xac\x1e\xf1\x78\ -\xbb\x5d\xe4\xf3\xf2\x7e\x68\x8b\x8f\x02\xe6\x8e\x54\x11\x6d\xd0\ -\x3b\x12\xcd\xd8\x01\x7c\x26\x88\x96\x95\xe7\x19\xd6\xf1\xd9\x05\ -\x2c\x18\x71\x02\xc0\x82\xca\xd2\x7d\x34\xdf\xa6\xe0\x67\x26\x3b\ -\x66\xb8\x14\x44\xce\x74\x11\xc6\x24\x00\x57\x0c\x57\x91\x1e\xaf\ -\x77\x24\x3a\x11\x68\x00\xe1\x23\x52\xa5\x25\xd3\xf3\xc5\x21\xbd\ -\xc3\x61\xec\x2c\x4e\x00\x98\xee\x76\xb4\x50\x12\xd9\xd5\x27\x0e\ -\xd4\x88\x85\xad\x76\x91\xa6\x77\x3c\xcc\xbf\xc6\xea\xdf\x01\x30\ -\x88\xd0\x4e\x00\x4b\x63\xa2\x0c\x1f\x4e\xcc\x12\x56\xbd\xa3\x61\ -\x91\x8d\x13\x00\xa6\x9b\xa2\x7a\x1a\x27\x91\xfa\x78\xb3\x4d\x7c\ -\xff\x50\xbd\x48\xb2\xba\xf5\x8e\x88\xf9\x5b\x94\x04\xdc\x34\x56\ -\x85\x51\xd2\x3b\x92\xe0\x42\x80\x45\x22\xbc\x4f\x90\x5e\x2d\x1c\ -\x22\x0e\xea\x1d\x0f\x8b\x4c\x9c\x00\x30\xcd\xdc\xb7\x82\x0c\x57\ -\x66\x23\xfd\xca\x11\xca\x5c\x59\x48\xcf\xb6\x5a\x70\x5d\x55\x07\ -\xd0\x68\xe1\x5f\xc3\x70\x35\x24\x85\x30\x63\x08\xdf\xfd\xf7\xa2\ -\x98\x04\xfe\xe1\x52\xa4\x77\x67\xe7\x0b\x87\xde\xc1\xb0\xc8\xc1\ -\xef\xbc\x2c\xe0\x7e\xf6\x29\x25\xa5\x27\xe1\x05\x99\xf0\x82\x42\ -\x48\x89\x96\x20\x5c\x32\x10\x92\xed\x60\x22\x5c\x7c\x34\x30\x24\ -\x99\x90\x12\x0b\x78\x14\xa0\xde\x02\x34\x5b\xbb\x7f\x1b\x99\x94\ -\x4d\x18\x39\x88\x13\x00\x1f\x35\x43\xe0\x1f\xaa\x5b\x7a\x95\x7b\ -\x0b\x30\x2d\x70\x02\xc0\x02\x66\xe1\x46\x2a\x88\x33\xe0\xbf\x9d\ -\x0a\xee\x57\x08\xd1\x7a\xc7\xc3\xfa\x2f\x29\x06\x18\x93\x41\x18\ -\x92\x42\x10\x17\xbc\x6b\x1c\x6a\x10\x38\xd5\xde\xf5\x5b\xc9\x94\ -\x5c\xc2\xf0\x34\x4e\x00\xfa\xc8\x0d\x21\x3e\x85\x2a\x5e\x2a\x1c\ -\x22\x76\xeb\x1d\x0c\x0b\x5f\x9c\x00\x30\xbf\xfb\xed\x16\x9a\x19\ -\x6b\xc0\x9f\xad\x6e\xcc\x06\xf1\xef\x58\x28\x33\x1a\x80\x71\x99\ -\x84\x11\x83\x08\xdd\x3d\xc6\x3f\xde\x22\x70\xbc\xf9\xe2\x1f\xb3\ -\x80\xb7\xfa\x7f\x50\xa4\x56\xff\xfb\x81\x00\xed\x50\x05\x5e\x2a\ -\xcc\x31\xac\x16\x42\xf0\xa2\x19\xf3\x2b\x7e\x73\x66\x7e\xb1\x70\ -\x21\x49\x62\x36\xee\x89\x8e\xc2\xef\x1c\x1e\x8c\xd5\x3b\x1e\x36\ -\x30\x42\x00\x43\xd3\x08\xe3\x32\x09\xb1\x3d\x1c\xa5\xe4\x56\x80\ -\x2f\x4f\x49\xb0\x5f\x50\xc0\x29\x00\x4c\x08\xae\xa3\x7f\x43\x9a\ -\x20\x9c\x82\x84\xbf\x75\xba\xa4\x37\xf8\x50\x22\xe6\x2f\x9c\x00\ -\xb0\x01\x59\xb8\x82\xa2\xa5\x41\xf8\xbe\x00\xfe\xdd\xa3\x20\x43\ -\xef\x78\xd8\xc0\x65\x25\x12\x2e\xc9\x26\x24\xc7\xf4\xfc\x3a\xb3\ -\x13\x28\xaa\x95\x60\x76\x9d\xff\xf1\x28\x09\x98\x3e\x84\x90\x1d\ -\x5e\x2d\x7f\x83\x45\x13\x01\xaf\x4b\x6e\xe9\x2f\xd3\x86\x8b\x4e\ -\xbd\x83\x61\xa1\x8d\x13\x00\xd6\x2f\x0b\x57\x50\x74\x54\x06\x7e\ -\xa0\x12\xfe\xd3\xa3\x20\x45\xef\x78\xd8\xc0\xa5\xc5\x01\xe3\xb2\ -\x08\x83\x13\x7b\x9e\xb8\x89\x80\x8a\x76\x81\xe3\x4d\x02\x17\x76\ -\x68\x34\x08\xe0\xaa\x11\x2a\x52\x62\x03\x18\x28\x03\x80\x4e\x00\ -\x7f\x33\xca\xd2\x5f\x26\x0f\x15\x1d\x7a\x07\xc3\x42\x13\x27\x00\ -\xac\x4f\xbe\x5b\x44\x51\x05\x26\xfc\x80\x80\xff\xf4\x28\x48\xd5\ -\x3b\x1e\x36\x30\xb1\x46\xef\x1d\xff\xd0\x34\xf2\xe9\x59\xbd\xac\ -\x02\xfb\x6b\x04\x9a\xba\xa9\xfc\x1f\x9a\x46\x98\x96\xcb\x77\xfe\ -\x1a\xb2\x02\x78\x25\x46\x95\xfe\x8f\x8f\x28\x66\x7d\xc5\x09\x00\ -\xf3\xc9\x77\x8b\x28\x6a\x98\x19\xdf\x53\x88\xfe\xcb\xa3\x08\x9e\ -\xf8\x07\xc0\x28\x01\x83\x12\x08\x83\x13\x81\xd4\x38\x42\x8c\x01\ -\x88\x36\x7a\xef\xac\x01\x40\x56\x00\x97\x02\x98\x9d\x02\x56\x37\ -\xd0\x6a\x13\xe8\xe8\x62\x77\x78\xac\x11\xc8\x4b\x21\xa4\xc7\x01\ -\x49\x31\x84\x18\x23\xce\xab\xd0\x97\x15\xc0\xad\x7a\xff\x5f\x56\ -\xcf\xbf\x5b\x8f\x92\x80\xb8\x28\x42\x52\x2f\xcb\xfc\xe7\x72\x2b\ -\xc0\xee\x6a\xa9\xcb\x58\xce\x9a\x30\x98\x30\x86\x9f\xfb\xeb\xc1\ -\x0a\xe0\xcd\x28\xa3\xf4\x3f\x93\x06\x8b\x26\xbd\x83\x61\xa1\x81\ -\x13\x00\xd6\xa3\x85\x5b\xc9\x68\x00\x9e\x82\x82\xff\xf1\xa8\x48\ -\xd7\x3b\x9e\x50\x66\x10\xc0\xa8\x0c\xc2\xa8\x0c\x42\x54\x1f\x3b\ -\xe3\xb5\x3b\x80\xbd\x67\x24\xb8\x64\xef\xbf\x8f\x1c\x44\x98\x30\ -\x98\x60\xd0\xe8\x2f\xd8\xe1\x01\x76\x55\x4b\xb0\xb8\x7a\x7e\xdd\ -\x88\x74\xc2\xe4\x1c\x4e\x00\x74\x64\x05\xf0\x8a\xea\x94\xfe\x30\ -\x63\xa4\x30\xe9\x1d\x0c\x0b\x6e\xdc\xa0\x93\x75\x8d\x48\xfc\xd7\ -\x7a\x7a\xcc\xa0\xa2\xce\xe3\xc1\xeb\x3c\xf9\x0f\x4c\x8c\x11\xb8\ -\x72\xb8\x8a\xf1\x59\x7d\x9f\xfc\x01\x20\x3d\xce\xdb\x55\xef\x2c\ -\x01\x68\x36\xf9\x5b\x5c\xc0\xb6\xaa\xde\x27\x7f\x00\xa8\x35\x09\ -\x38\x3c\x81\x8f\x89\x75\x2b\x11\xc0\xcf\xa5\x58\xb5\xb2\xa4\x4e\ -\xf9\xf9\xd6\x2a\xe2\x6a\x0c\xd6\x2d\x5e\x01\x60\x17\xf9\xaf\x4d\ -\x74\x4d\x94\x84\x37\x9d\x32\x86\xe9\x1d\x4b\x38\x90\x04\x70\xd5\ -\x70\x15\xa9\x71\x03\xbb\x4e\x55\x87\xc0\xc1\xfa\x6f\xff\x64\x53\ -\xe3\x80\xb1\x19\x84\xec\xe4\xc0\x35\x5b\xb0\xb8\x80\x1d\xa7\xbf\ -\x5d\x79\xf0\x45\x8c\x11\xc8\x4f\x21\x64\x26\x02\xf1\x51\xde\x47\ -\x13\x11\x74\x0c\x70\xb0\x39\x43\xa0\x85\x95\xb9\x86\x77\xf8\x48\ -\x62\x76\x21\x4e\x00\xd8\x37\x7e\xbd\x81\x46\xc7\x1b\x69\xa9\x53\ -\x16\x73\xf4\x8e\x25\x9c\x0c\x4b\x23\x4c\xf5\x43\x61\x9c\xc9\x09\ -\x6c\xad\xbc\x78\xf9\x20\x23\x01\x98\x59\xa0\xf6\x6b\x65\xa1\x27\ -\x66\xa7\x77\xd9\xdf\xd9\x87\xc9\xbf\x3b\x06\x09\x48\x8e\xf1\xae\ -\x62\x0c\x4d\x23\x3e\x1c\x48\x7b\xc7\x49\xa8\x2f\x4c\xcf\x8d\x5a\ -\xa7\x77\x20\x2c\x78\x70\x02\xc0\xb0\x70\x1d\xa5\xc7\xc6\xe2\x25\ -\x87\x07\x0f\x53\xf7\x0d\xdf\x58\x3f\x4d\xcf\x23\xe4\xa7\xfa\xe7\ -\xb9\x78\x69\x83\xc0\xe9\x2e\xda\xee\xfa\xbb\xf8\xce\xec\x02\x76\ -\xf6\xf1\xce\xdf\x57\xa9\xb1\xc0\x95\x23\x54\xcd\x1e\x61\xb0\x6f\ -\x09\x88\x35\x8a\x22\x7e\x32\xa3\x40\x54\xea\x1d\x0b\xd3\x1f\xff\ -\x09\x46\xb0\x85\x2b\x28\x3a\x26\x13\xbf\xf4\x28\xf8\xa5\xac\xa2\ -\x0f\xf5\xe0\xac\xaf\x92\x63\x81\x84\x68\xef\x9d\xaf\xa2\x7a\x0f\ -\xd2\xf1\xa8\x02\x1e\x19\x20\x01\xc4\x18\x80\xb4\x38\xc2\xc8\x0c\ -\x42\x42\x54\xcf\xd7\xaa\x33\x79\x7b\xef\x77\x38\x00\x95\xbc\xcb\ -\xeb\xa3\x06\x11\xc6\x64\xfa\x27\x01\x30\x3b\xbd\xcb\xfe\xee\x00\ -\x2e\x18\xf3\x21\x41\xba\x72\x01\xf8\x8b\x3d\x5a\xfa\xfd\x15\x99\ -\xc2\xa2\x77\x30\x4c\x3f\x9c\x00\x44\xa8\xdf\x6d\xa2\x7b\x85\x84\ -\x57\xdd\x32\x32\xf5\x8e\x85\x7d\x2b\x4a\x02\xe6\x8e\x54\x91\xa0\ -\xd3\xd1\x49\x76\x0f\xb0\xfd\x94\x04\x47\x00\xee\xfc\xcf\x95\x9b\ -\x4c\xb8\x2c\x9f\x13\x00\x9d\xb5\x0a\x81\xff\x2e\xcf\x91\x5e\xe6\ -\xfa\x80\xc8\xc4\x09\x40\x84\xf9\xf5\x17\x34\x36\x36\x16\xef\xbb\ -\x3d\x98\xa6\x77\x2c\xac\x6b\xc3\xd2\x09\x53\x75\xd8\x4a\xe7\x92\ -\x81\xed\x55\x12\xac\xee\xde\x5f\x3b\x50\xfe\xaa\x8b\x60\x7e\x51\ -\x02\x21\xbd\x50\x98\x2b\xb6\xe9\x1d\x08\xd3\x16\x27\x00\x11\x62\ -\xe1\x56\x8a\x8d\x06\xfe\xe0\x91\xf1\x23\x85\xc0\x35\xd9\x41\x4a\ -\x08\xe0\x8a\xa1\x2a\x06\x25\x68\x3b\xae\xac\x00\xdb\xab\x25\x98\ -\x7a\x68\xf2\xe3\x4f\x53\x72\x08\xc3\xd3\x39\x01\x08\x26\x02\x62\ -\x8d\x64\x10\xcf\x4f\xc9\x16\x55\x7a\xc7\xc2\xb4\xc1\x05\x5f\x11\ -\xe0\x77\x1b\xe8\x56\xa3\x8a\x5a\xa7\x07\x3f\xe1\xc9\x3f\xb8\x8d\ -\xcb\x24\xcd\x27\x7f\x22\x60\x5f\xad\xd0\x6c\xf2\x17\x00\x1f\x14\ -\x14\x84\x08\x74\xbb\xa2\xa8\x47\x0e\xd4\x29\xff\xb6\x95\xa8\x87\ -\x33\x20\x59\xb8\xe0\x15\x80\x30\xf6\xab\x75\x94\x93\x18\x8d\x37\ -\x9d\x32\x6e\xd6\x3b\x16\xd6\xbb\xa1\xa9\x84\xa9\x79\x81\xdb\xd3\ -\xdf\x9d\xc3\x8d\x02\x95\x6d\xda\x8d\x9a\x16\x07\x5c\x3d\x82\x8f\ -\xb6\x0f\x72\x07\x85\x24\x3d\x33\x2d\x47\xec\xd7\x3b\x10\x16\x38\ -\x9c\x00\x84\xa1\x85\x0b\x49\xc2\x95\x78\x52\x22\xfc\x4d\x56\x31\ -\xc0\xf6\x33\x4c\x0b\x39\x49\xde\xa2\x38\xa1\xf1\x5f\x64\x4d\xa7\ -\x40\x71\x9d\xb6\x83\x4e\xc8\xf2\xdf\x8e\x05\x16\x50\x32\x80\x57\ -\x0d\x06\xe9\x57\x53\xb2\x85\x4d\xef\x60\x98\xff\xf1\x23\x80\x30\ -\xb3\x70\x03\x4d\x95\xae\xc0\x61\x55\xc1\x22\x9e\xfc\x43\x43\x66\ -\x02\xe1\x52\x1d\x26\x7f\x93\x03\x28\xad\xd7\xfe\x1e\xa0\xc3\xa9\ -\xf9\x90\xac\x7f\x8c\x00\x9e\x57\x65\xf5\x50\x49\x9d\xe7\x7a\xbd\ -\x83\x61\xfe\xc7\x09\x40\x18\x39\x50\x45\xa9\x83\x93\xe8\x5d\x59\ -\xc5\x04\xbd\x63\x61\xbe\x49\x8e\x05\x66\x16\x10\x24\x1d\xd6\xe2\ -\xa2\x0c\xde\x53\x09\xb5\xd6\x60\x16\x68\xb1\xf1\xe2\x63\xa8\x20\ -\x81\x11\x80\xb4\xa1\xa4\x56\x79\x67\x6f\x2d\x0d\xd2\x3b\x1e\xe6\ -\x3f\xfc\x57\x18\x26\x8a\xeb\xe4\x3b\x04\xc4\x6b\x0e\x0f\xf2\x36\ -\x96\x4b\x50\x79\x85\x35\xe8\x49\x02\xb8\x76\x94\x8a\x44\x9d\xf6\ -\xfc\x9f\x65\xf3\x00\x1d\x76\xef\x21\x3e\x17\x36\xff\x91\x55\x6f\ -\xb3\x21\xa7\x07\x30\xbb\xfc\x77\xd0\x4f\x4e\x32\x61\x26\xf7\x01\ -\x08\x45\x4d\x20\xfa\x59\xe1\x10\xe3\x3b\x7a\x07\xc2\x06\x8e\x13\ -\x80\x10\x57\x72\x86\x72\x61\xa0\xd7\x01\xba\xe3\xec\xc7\x76\x55\ -\x0b\x34\x5b\xf9\x47\x1b\xec\x46\x0f\x22\x4c\xcc\x0e\xad\x49\xd0\ -\xad\x00\x66\xa7\x80\xc9\xe9\x6d\x17\xec\xf0\x00\x4e\x59\xc0\xe1\ -\x06\x3c\x7d\xa8\xeb\x8b\x31\x02\xb7\x8c\xe5\x42\xc0\x50\x45\x10\ -\xab\xc9\x23\xbe\x3f\x63\x98\x68\xd0\x3b\x16\xd6\x7f\x3c\x4b\x84\ -\xb0\xe2\x7a\xf9\x6e\x41\x62\x11\x80\xf3\x96\xe5\x0e\x35\x78\x5b\ -\xc5\xb2\xe0\x76\xc3\x18\xb5\xd7\xb6\xbf\xa1\xc4\xad\x00\x76\x37\ -\xd0\x66\x17\xa8\x6c\x17\xb0\xf7\xd0\x50\xc8\x20\x80\x3b\x26\x70\ -\x02\x10\xe2\x5a\x05\xe8\xe9\x69\x79\xc6\x4f\xf5\x0e\x84\xf5\x0f\ -\xd7\x00\x84\xa0\x5d\x35\x14\x57\x52\xa7\xfc\x55\x90\x58\x85\x0b\ -\x26\x7f\x00\x01\x39\xc0\x85\xf9\x57\x7c\x34\xc2\x6a\xf2\x07\xbc\ -\x67\x12\xa4\xc6\x01\x23\x07\x11\xae\x1d\xa9\x62\x70\x0f\x7b\xfd\ -\xf5\x6a\x75\xcc\xfc\x2a\x83\x20\x56\x97\xd4\x2a\xef\x94\x35\x53\ -\xa2\xde\xc1\xb0\xbe\xe3\x04\x20\xc4\x94\xd4\xd3\x8c\x58\x49\x2d\ -\x05\xf0\x7c\x57\x9f\xb7\xb9\x81\x06\x0b\xdf\xfd\x07\x3b\x63\x98\ -\xff\x88\x8c\x12\x30\x2b\x9f\x30\xa2\x9b\x6e\x7f\x63\x79\x1b\x60\ -\xf8\x10\x78\xc4\xe9\x51\x8b\x8a\xeb\x69\xba\xde\xa1\xb0\xbe\x09\ -\xf3\xb7\xa1\xf0\x41\x44\xa2\xb4\x41\x7d\x9e\x08\xff\x07\xa0\xcb\ -\xfb\xa7\x56\x9b\x77\x4f\xb7\xbf\x0a\xb5\x58\xe0\x24\x46\x03\xd7\ -\x8f\xf6\xef\x12\xb8\x4a\x00\x44\xf0\x65\xf5\x75\x66\x81\x53\x6d\ -\x02\x2e\x19\x88\x8f\x26\x8c\x1c\x04\x0c\x4e\xe4\x04\x20\x0c\xc9\ -\x04\xfc\xbe\x32\x57\xfa\x1d\x1f\x2e\x14\x1a\x38\x01\x08\x01\xfb\ -\xea\x28\xdf\x48\xca\xbb\x10\xe2\x6a\x59\xf5\x56\x64\x3b\x65\x01\ -\xab\x0b\xb0\xb8\x01\x8b\x0b\xb0\xba\x7b\x7e\xe6\xca\x82\x8b\x10\ -\xc0\x8d\x63\x54\xc4\xf9\xa1\xe1\x6a\x93\x45\xe0\x58\x8b\x40\xa7\ -\x03\xc8\x4f\x25\x4c\xcf\xe3\xc9\x95\xe9\x87\x84\xd8\x6a\x54\xc5\ -\xa3\x53\x86\x88\x5a\xbd\x63\x61\x3d\xe3\x04\x20\xc8\x2d\xde\xa7\ -\xfc\xa5\xcd\x2e\xfd\x50\x56\x61\x50\x54\xef\xb6\x2c\x16\x1e\x06\ -\x7a\x22\x1e\x01\x38\xda\x28\x50\x7e\x4e\x1b\x5f\x01\xe0\x9a\x91\ -\x2a\x92\x63\x07\x1e\x1f\x63\xfd\x26\x60\x12\xa0\xef\x4f\xcb\x35\ -\x2e\xd7\x3b\x14\xd6\x3d\x4e\x00\x82\xd4\x3b\x15\x94\x65\x6d\xc5\ -\xf6\x46\x33\xc6\xe8\x1d\x0b\x0b\x9c\x59\x05\xd4\xaf\x83\x71\x88\ -\x80\xd2\x06\x81\xea\x8e\x8b\xff\x84\xa7\xe6\x12\x86\xa5\xf1\x2a\ -\x00\x0b\x0a\x6f\xc6\x44\x49\x3f\x9e\x98\x25\xac\x7a\x07\xc2\x2e\ -\x16\x6c\x8f\x0b\x19\x80\x77\x8a\xe9\xa6\xa6\x3a\x54\xfb\x63\xf2\ -\x8f\x36\x00\x19\x09\x84\xdc\x64\xc2\xa0\x78\x68\xde\x6e\x96\xf5\ -\x6c\x7f\xad\x40\x53\x17\x45\x9b\x66\x17\x50\xde\x26\x50\xd3\x29\ -\x40\x17\xcc\xe5\x44\xc0\x81\xfa\xae\x27\x7f\x00\x88\x0d\xb3\xdd\ -\x05\x2c\xa4\x3d\xe9\xf4\xa8\x45\x25\x0d\xc4\xdd\x49\x83\x10\x4f\ -\x07\x41\x66\xd1\x3e\xfa\x53\xa3\x05\x3f\xf5\x28\xdf\xfe\x6c\xa2\ -\x24\x20\x3e\x06\x88\x96\x08\x06\x01\x98\x7c\xe8\xc8\x96\x95\x48\ -\x18\x9b\x41\x48\xbf\x60\xd2\xb7\xb9\x81\xf2\x56\x81\x33\x9d\xe2\ -\xa2\x6e\x81\x67\x5f\x77\xe1\x84\xc3\x02\x4b\x08\x60\x64\x3a\x21\ -\x2f\x85\x20\xab\x02\xa7\xda\x81\x46\xb3\xc0\xd9\x1f\xc3\xf0\x74\ -\xc2\xe4\x1c\xef\x29\x81\x1e\x15\x28\xea\x26\x69\x00\xbc\xdb\xf0\ -\xae\x1e\xae\x72\xa2\xc7\x82\x0a\x01\x16\x41\xf4\x64\xe1\x10\xe3\ -\xc7\x7a\xc7\xc2\xbe\xc5\x6f\x13\x41\x62\x61\x19\x25\xa6\x77\xe2\ -\xab\x56\x1b\x0a\x25\xe1\x9d\xc0\x73\x92\x80\xf4\x78\x42\x62\xf4\ -\xf9\x93\x38\x11\xb0\xb5\x52\x82\xd9\x75\xf1\x75\x84\x00\xa6\xe6\ -\x10\x86\xf6\xb2\x04\xec\x94\x81\x3a\x93\x80\xd9\xe5\xed\xca\x96\ -\x1e\x0f\x0c\x8a\x23\x18\x25\xa0\xc6\x2c\x70\xa8\x5e\x70\xbd\x41\ -\x10\xc9\x49\x22\x24\xc7\x02\xa7\x3b\x44\xb7\x7d\x1e\xd2\xe2\x80\ -\xcb\x87\xaa\x88\x36\x68\x1b\x1b\x63\x3e\x22\x00\xff\x57\x91\x2b\ -\xfd\x3b\xef\x12\x08\x0e\x9c\x00\x04\x81\x37\x8a\xa8\xd0\x64\xc3\ -\x97\x1e\x05\x49\xa3\x06\x79\x9f\xdf\x46\xf5\xf0\x26\xee\x51\x81\ -\xf5\x27\xa4\x2e\x27\xe8\x58\x23\x70\xb3\x1f\x5a\xac\x36\x98\x05\ -\xf6\xd6\xf0\xaf\x47\x28\x30\x48\xc0\x98\x0c\xc2\xe8\x0c\x7d\x0e\ -\x15\x62\xac\x4f\x04\x7d\x69\x90\x0c\xf7\x4f\xc9\x16\xcd\x7a\x87\ -\x12\xe9\xb8\x06\x40\x67\xaf\xef\xa5\xe7\x9a\x3b\x69\xbf\x00\x92\ -\xae\x19\xa9\x62\x74\x46\xcf\x93\x3f\x00\x94\x35\x75\x7f\x77\xee\ -\x94\x81\x56\xfb\xc0\xe3\xca\x49\x26\x24\xc7\x0c\xfc\x3a\x2c\x70\ -\xa2\x24\x60\x74\x06\xe1\x86\xd1\x2a\xc6\x66\xf2\xe4\xcf\x42\x04\ -\x89\xb9\x8a\xa2\x16\x95\xd6\xd0\x65\x7a\x87\x12\xe9\xf8\x2d\x43\ -\x47\xaf\xed\x52\x96\x37\x5a\xa5\x07\x0c\x12\x70\xcd\xa8\xde\xfb\ -\xc2\x77\x38\x80\x63\xcd\xbd\x1f\xf4\x93\x12\x07\xcc\xf5\xc3\x73\ -\xe0\x9e\x0a\xcd\x98\x3e\x84\x00\x32\xe2\x09\x05\xa9\x40\x6e\x32\ -\xc1\xc0\x29\x3c\x0b\x5d\x2e\x21\xe8\x87\xd3\x72\x8d\x8b\xf5\x0e\ -\x24\x52\xf9\xa1\x0d\x09\xeb\xab\xa5\x07\x28\xd5\x62\xa7\xfd\x0d\ -\x56\x31\x0a\x00\x26\x66\x53\xb7\x93\x3f\x01\x68\xb6\x08\x9c\x68\ -\x15\x68\xf7\xf1\xce\xde\xe4\xf0\x36\x08\x1a\xe8\x1d\xbc\xca\x35\ -\x00\x3e\x33\x4a\xde\xa5\x78\xb7\xe2\xff\x22\x4a\x01\x6f\x71\x5f\ -\x4e\x12\x61\x48\x2a\x21\x9e\xab\xfc\x59\x78\x88\x21\x12\x8b\x4a\ -\x6a\x95\xab\x9c\x24\x3d\x3b\x3b\x5f\x38\xf4\x0e\x28\xd2\x70\x02\ -\xa0\xb1\xa5\xfb\xe8\xd2\x16\x33\x7d\x69\x75\x89\x78\x00\x18\x94\ -\x00\x0c\x4b\xed\x7a\xc6\x30\xbb\x80\x92\x3a\x09\x9d\x7d\xfc\xb3\ -\x48\x8e\xf5\xd6\x02\x0c\x84\xa2\x02\xcd\x36\xbe\xfb\xef\x4e\x72\ -\x0c\x90\x9b\x42\xc8\x48\x20\xa4\xc6\x7a\x13\x00\xc0\xfb\x7d\xdb\ -\x50\x2e\x0d\xf8\x40\x26\xa3\x01\xc8\x88\x23\x64\x27\x01\xd9\xc9\ -\x34\xe0\x9f\x27\x63\x41\x4b\xe0\x91\x58\xa1\x8c\xde\xd7\x4c\x77\ -\x5f\x96\x25\x1a\xf5\x0e\x27\x92\xf0\x3b\xbc\x86\x16\xef\xa7\x47\ -\x9a\x2c\x58\xea\x92\x61\x00\xbc\xcf\x70\xe7\x8e\x54\xbb\x3c\x19\ -\xad\xb2\x4d\xa0\xac\xe9\xe2\xad\x7a\xbe\xb8\x66\xa4\x8a\x14\x1f\ -\x3a\xc1\xc9\x2a\x70\xaa\x5d\xa0\xc9\x2a\x10\x25\x08\x83\x12\x80\ -\xac\x04\x6f\x0d\xc2\xb1\x16\xef\x1e\x74\xf6\x2d\x83\x00\x86\xa4\ -\x7a\x0f\xb8\xe9\xee\xfb\x7b\xb8\x51\xa0\xb2\xad\xef\xdf\x37\xa3\ -\xe4\xdd\xf1\x91\x11\x0f\x64\x26\x7a\x93\x0a\xde\xca\xc7\x22\x4c\ -\x1d\x48\xba\xad\x70\x88\x38\xa8\x77\x20\x91\x82\xdf\x62\x34\xf2\ -\xda\x5e\xfa\x7d\x93\x19\xbf\x3a\x3b\xa1\x0b\x01\xcc\xcc\xbf\xb8\ -\x0b\x1c\x01\x38\xd4\x20\x50\xd5\xde\xbf\x1f\x4d\x5a\x1c\x70\xf5\ -\x88\xde\xd7\xee\x9d\x32\xb0\xbb\x5a\x82\xc9\xd9\xaf\x61\x22\x4a\ -\x7c\x94\x77\x2f\xfe\xd0\x34\xea\x76\x8b\x9d\xc5\xe5\xfd\xb9\xb5\ -\xf8\xb8\x6a\x22\x09\xef\xd6\xcb\x8c\x04\x42\x56\x02\x21\x35\x0e\ -\x5c\xc4\xc7\x22\x1e\x01\x16\x08\x75\xc1\xf4\xdc\xa8\x75\x7a\xc7\ -\x12\x09\xf8\x2d\x27\xd0\x88\xc4\x2b\xbb\x69\x65\xb3\x55\xdc\x7d\ -\x76\xaa\x17\x02\x28\xcc\x23\xe4\xa7\x5c\x7c\x7b\xdf\xdf\x3b\xc8\ -\xb3\x86\xa5\x13\xa6\xe6\xf4\xbc\x6c\xe0\x51\x80\x6d\x55\x12\x2c\ -\x5d\xf4\x11\x60\xdf\xca\x88\xf7\x9e\x5c\x97\x9d\x44\xdd\xde\x8d\ -\x13\x80\x63\x4d\x02\x15\xad\x02\xbd\xa5\x5d\x42\x00\x99\x09\x84\ -\xbc\x64\xef\x2e\x0b\xde\xaf\xcf\x58\x97\x64\x12\xf8\xc1\xf4\x5c\ -\xc3\x3f\xf4\x0e\x24\xdc\xf1\x93\xc5\x00\xfa\xdb\x5a\x8a\xa1\x1d\ -\xd8\xdb\xe4\x10\x53\xce\x7e\x4c\x00\x98\x92\xdd\xf5\xe4\x5f\xd5\ -\x31\xb0\xc9\x1f\x80\x4f\x93\x4a\x69\x83\xe0\xc9\xbf\x0b\xdf\x14\ -\xdb\x25\x7b\x5b\x27\x27\x76\x79\xe8\xf2\xf9\xaa\xda\x05\x4e\xb6\ -\xf6\xfe\x33\x1b\x92\x42\x98\x98\x4d\x7e\x39\xfd\x8f\x69\xab\xe4\ -\x48\x39\x52\x93\x13\x31\xa2\x20\x47\xef\x50\x22\x85\x51\x10\xde\ -\x28\xa9\x53\x26\x4e\xcb\x95\x7e\x22\x84\xe0\xde\xa4\x01\xc2\x6f\ -\x47\x01\xf2\xca\x56\xca\x76\x4a\x28\x35\x39\x30\xf8\xdc\x8f\x4f\ -\xc8\x26\x0c\x4b\xbf\xf8\xf7\xd9\xec\x04\x0e\x37\x0c\x7c\x41\xc6\ -\xdd\x4b\xf1\x59\xbd\x59\xa0\xce\xc4\x0b\x3f\x67\x89\xaf\x97\xe2\ -\x73\x93\xbc\x93\x7e\x5c\x1f\x2b\xec\x9b\x7b\x39\xe2\x24\xda\x00\ -\x4c\xc9\x25\xe4\x25\xf3\x7b\x58\xa8\x69\xed\x30\xe1\xb5\x65\x6b\ -\xb0\x61\x5b\x31\x5e\xfc\xf5\x73\x9c\x00\x68\xef\xf9\xd2\x7a\x35\ -\x7b\x6b\x15\x3d\x76\xcd\x70\xc1\x0f\x2b\x03\x80\x13\x80\x00\x78\ -\x79\x37\x15\x5a\x5c\xd8\x6e\x77\x21\xfe\xdc\x8f\x8f\xcd\x24\x8c\ -\x1e\x74\xf1\x44\x40\x00\x0e\x36\x4a\xfd\x2a\xf8\xbb\x90\xc9\x29\ -\xbe\xbe\xe2\xc5\xdc\x0a\x70\x70\x80\x49\x86\x10\xde\x67\xe2\xb1\ -\x51\x40\xb4\x81\x10\x63\xf0\x16\xb0\x49\xc2\xfb\x3f\xa7\xec\x3d\ -\x6f\xa0\xdd\x2e\xa0\x04\xe9\x9c\x27\xce\x69\xb5\x9c\x93\x44\x88\ -\x19\xc0\x5f\x41\x4f\x4d\x9b\x06\x27\x11\xa6\xe5\x72\x05\x7f\xa8\ -\x91\x15\x05\xab\xd6\xed\xc4\xa2\x0f\xd6\xc2\xe1\xe0\xa5\x32\x3d\ -\x11\xb0\x20\x25\x5a\xc9\x2b\xaa\xa7\x79\x33\x72\x45\xab\xde\xf1\ -\x84\x1b\x7e\x6b\xf2\xb3\xc5\x7b\xe9\xb6\x46\x2b\x56\xbb\x95\xf3\ -\xbf\xb7\x79\x29\x84\xf1\x59\x5d\xcf\x88\x8d\x66\x81\x36\x9b\x7f\ -\xc6\xef\x74\x7a\x27\xe1\xae\x26\x9d\xb2\xa6\xee\xfb\xc8\xf7\x26\ -\x27\x99\x30\x22\xdd\x5b\xa9\x6e\xf0\x21\x87\x70\x78\x08\x1b\xcb\ -\xfd\x93\xd4\xf8\x4b\x52\x0c\x50\x90\xe6\x7d\xfc\xe2\xaf\x49\x39\ -\x33\x11\xa8\xe9\x3c\xff\x63\x71\x51\xc0\xe4\x6c\x42\x0e\xdf\xf5\ -\x87\x9c\x92\xb2\x0a\xbc\xb4\x78\x25\xaa\x6a\x78\x37\x5a\xf0\x10\ -\x73\x24\x52\x77\x1f\xa8\xa1\x5b\xa7\xe5\x8b\x72\xbd\xa3\x09\x27\ -\x9c\x00\xf8\xd1\x92\xbd\xf4\x5c\xbd\x05\xaf\x7a\xd4\xf3\x8b\x2b\ -\xe3\xa2\x80\x29\x3d\x14\xe6\xf9\xf2\x0c\xd9\x57\x44\x40\x45\xab\ -\xc0\x25\xd9\xe7\x8f\x57\x6f\xee\x7f\x57\xbf\x31\x19\x84\x09\x83\ -\xfb\x36\x99\x35\x5a\xfb\xb7\x85\x31\x10\x52\xe3\x80\x71\x99\x84\ -\xc1\x49\xe4\xf7\xaa\xd7\xfc\x64\x82\x65\x90\xf7\xbf\x37\x3e\x8a\ -\x90\x9f\xea\xed\x0f\xc0\x0d\xfa\x42\x4b\x4b\xbb\x09\xaf\xbf\xb7\ -\x06\xeb\xbf\x2a\xd2\x3b\x14\xd6\xb5\x51\x24\xa9\xdb\x8b\xea\xe9\ -\xd6\x19\xb9\xa2\x44\xef\x60\xc2\x05\x27\x00\x7e\xb2\x68\xaf\xf2\ -\xef\x35\x66\xfa\x6f\x95\x2e\x9e\x62\xa6\xe5\x76\x5f\xf1\xdd\xee\ -\xf0\xb6\xf8\xf5\xa7\x53\x6d\x02\x19\x09\xf8\x66\x8b\x61\xab\x4d\ -\xa0\xa4\xbe\xff\x53\x5f\xbd\x59\xc0\x68\xf0\xf6\x08\x48\xe9\x65\ -\x7f\xba\x43\xf6\x8e\x5f\x31\xc0\x62\x46\x7f\x88\x31\x7a\x4f\x46\ -\x0c\xe4\x9d\xb8\x10\xde\x4e\x8e\x13\xbb\x79\xec\xc2\x82\xdb\xd9\ -\xe5\xfe\xc5\xef\xaf\x85\xdd\xc9\xcb\xfd\x41\x6e\xb0\x04\x75\xcb\ -\x81\x3a\xba\x6d\x5a\x9e\xd8\xa9\x77\x30\xe1\x80\x13\x00\x3f\x78\ -\x6d\x37\xfd\xb5\xd6\x84\xe7\xbb\x9a\x02\xf2\x92\x09\x59\x89\xdd\ -\x4f\x0e\x35\x01\xe8\xb5\xaf\x02\xd8\x5b\x23\x90\x9d\xe4\x3d\xed\ -\xa9\xc1\xdc\xfb\x16\xb5\x9e\x58\xdd\xc0\xd1\x26\x81\xa3\x10\x30\ -\x4a\x40\x42\x0c\x90\x18\x45\x88\x32\x7a\x9b\x19\xa9\x04\xb8\x64\ -\xc0\xec\x14\x5d\x1e\x51\xac\x87\xac\x44\xc2\xf4\xbc\x81\x3d\xdf\ -\x67\xe1\xad\xf8\x70\x39\x5e\x5c\xb2\x12\xd5\xb5\x4d\x7a\x87\xc2\ -\x7c\x45\x48\x21\xa8\x1b\x0e\xd4\x79\xe6\x4d\xcb\x8b\xda\xa8\x77\ -\x38\xa1\x8e\xdf\x1e\x07\xe8\x95\xdd\xf4\x41\xa3\x05\xf7\x77\x35\ -\xc5\x4b\x02\x17\x2d\xc5\x9f\x8b\x08\xa8\x33\x07\xe6\x4e\x99\xc8\ -\x3b\xf1\xfb\x9b\xac\x7a\xcf\x1a\x30\x39\xf4\xbf\xc3\xef\x4e\x7e\ -\x2a\xa1\x30\xb7\xfb\xbd\xfb\x2c\xb2\x35\xb7\x75\xe2\x8d\xe5\xff\ -\xe4\xe5\xfe\xd0\x15\x4f\x90\x3e\x2f\xae\x97\x1f\x98\x9e\x6b\xfc\ -\x44\xef\x60\x42\x19\x27\x00\x03\xf0\xf2\x2e\x75\x53\x93\x05\xd7\ -\x75\xf7\xf9\x82\xd4\x9e\xb7\x95\x75\x3a\xbd\x95\xf9\xcc\x7f\xd2\ -\xe2\x80\x69\x79\xfe\x7f\xd6\xcf\x42\x9f\xcb\xed\xc1\x7b\xab\x37\ -\x63\xd9\x27\x5b\xe0\xf6\x78\xf4\x0e\x87\x0d\x4c\x8c\x80\xf8\xb0\ -\xb8\x56\x7e\x78\xfa\x10\xe3\x0a\xbd\x83\x09\x55\x9c\x00\xf4\x07\ -\x91\x78\x65\x17\xbe\x6a\xb2\xe2\xca\x9e\x5e\x36\x2a\xa3\xe7\xe7\ -\xc2\x81\xb8\x43\x8f\x64\x02\xc0\xd4\x5c\xd5\xa7\x02\x3c\x55\x25\ -\x58\x6d\x76\x98\x6d\x0e\x58\xac\x76\xd8\xec\x4e\xd8\x1d\x2e\xb8\ -\x3c\x1e\x38\x1c\x2e\xd8\x9d\x2e\xb8\xdd\x1e\xd8\xbe\xde\x06\xa6\ -\x28\xca\x45\xcf\x88\xdd\x6e\x0f\xdc\xee\xf3\x27\x12\xa7\xcb\x03\ -\x83\x41\x42\x94\xf1\xdb\xa2\x8f\xa8\xe8\x28\xc4\x44\x7b\x33\xc1\ -\x98\xa8\x28\x44\x47\x47\x21\x3e\x2e\x1a\x46\x83\x01\x49\x09\xf1\ -\x30\x18\x24\x24\xc4\xc7\x21\x39\x29\x1e\x49\x09\x71\x48\x4a\x88\ -\x43\x72\x62\x02\xe2\xe3\x06\x78\x9c\x23\xfb\xc6\xf6\xfd\x47\xf0\ -\xf7\xb7\x56\xa3\xbe\xa9\x4d\xef\x50\x98\xbf\x10\xa2\x84\x10\xcb\ -\x4b\xea\xe4\xc4\xc2\x3c\xe3\x9b\x7a\x87\x13\x8a\x38\x01\xe8\x2b\ -\x22\xf1\xb7\x5d\xd8\xd9\x64\xc3\xe5\x3d\xbd\x2c\x2d\x0e\x3d\x76\ -\x92\x73\x78\xd0\xef\x7e\xff\xfd\x21\x04\x90\x14\x0d\x24\xc6\x10\ -\x12\xa2\xbd\x8f\x27\x5c\x32\xd0\xe9\x14\x30\x39\xfd\x7f\x84\xad\ -\x1e\x12\x25\x3b\xda\x9a\x3b\x50\x61\xb2\xa2\xad\xc3\x8c\x4e\xb3\ -\x15\x6d\x9d\x16\xb4\x77\x9a\xd1\x69\xb2\xa2\xc3\x6c\x83\xd9\x6a\ -\x83\xd9\x62\x87\xd5\x1e\xfc\x27\x8f\x1a\x0c\x12\x92\x13\x13\xbe\ -\x4e\x0a\xe2\x91\x9c\x18\x87\xa4\xa4\x78\xa4\xa7\x24\x21\x2b\x23\ -\x0d\x99\x83\x52\x90\x91\x96\x8c\xec\xcc\x74\xa4\xa7\x24\xc1\x60\ -\xe0\xbd\x07\x17\xaa\x69\x68\xc1\x5f\x97\x7e\x82\x3d\x25\xc7\xf4\ -\x0e\x85\x05\x86\x01\x10\x8b\x8b\xeb\x95\xe4\xe9\xb9\x86\xbf\xe8\ -\x1d\x4c\xa8\xe1\x04\xa0\x0f\x16\x2e\x24\x29\x7d\x17\xed\x6f\xb5\ -\x89\xc2\xde\x5e\xdb\x53\xe5\xb9\x47\x05\xf6\xd4\x48\xf0\x0c\xa4\ -\x32\xcf\x07\xd1\x06\x6f\x33\x9a\xc1\x89\xde\xa2\xb8\xae\x76\x22\ -\xd4\x98\xbc\xcd\x81\xe4\x10\x78\x14\xe1\xb0\xd9\xd0\xd1\xde\x02\ -\x53\x47\x2b\x4c\x1d\xed\xb0\x58\x3a\x61\x31\x75\xc2\xd4\xd1\x06\ -\x8b\xb9\x13\x6e\x57\x78\x35\x0b\x53\x14\x15\x1d\x26\x0b\x3a\x4c\ -\x96\x5e\x5f\x2b\x49\xd2\xd7\x89\x41\x2a\x86\xe4\x64\x60\x58\x5e\ -\x36\x0a\xf2\xb2\x30\x2c\x2f\x0b\xf9\xb9\x59\x11\x97\x1c\x38\x5d\ -\x6e\xbc\xb3\x6a\x23\x3e\xf8\xf4\x4b\xb8\xe5\x01\x9e\xcd\xcc\x82\ -\x9d\x10\x84\x97\x4a\xea\x94\xe4\xc2\x3c\xc3\x6f\xf5\x0e\x26\x94\ -\x70\x02\xe0\xa3\xfb\x56\x90\x21\x3d\x17\xc5\xad\xb6\x6f\xfb\xfa\ -\xf7\xa4\xab\x23\x7e\x01\xef\x79\xf1\xbb\xab\x25\x98\xfc\x74\x03\ -\x2a\x7d\xdd\x99\x2f\xda\x08\x44\x49\x84\x68\x23\x90\x12\xeb\x6d\ -\xd8\x93\xd6\xc3\x96\x3d\xb7\x02\x1c\x69\x14\x38\x13\x64\x47\xfe\ -\x7a\x3c\x6e\xb4\xb5\x34\xa2\xbd\xa5\x09\x1d\x6d\x2d\xe8\x68\x6b\ -\x41\x67\x7b\x2b\x3a\xda\x5b\xe0\x72\x06\xff\x5d\xbb\x5e\x54\x55\ -\x45\x6b\x87\x09\xad\x1d\x26\x1c\x2d\xaf\x3e\xef\x73\x7f\xff\xaf\ -\x1f\x60\xda\xc4\x51\x3a\x45\xa6\xbd\xad\x7b\x0e\xe2\xe5\xb7\x3e\ -\x45\x53\x6b\x87\xde\xa1\x30\x6d\xfd\x57\x49\x9d\x12\x5f\x98\x67\ -\xf8\x85\xde\x81\x84\x0a\x4e\x00\x7c\xb0\x70\x2b\x19\x53\x8d\x28\ -\x6d\xb5\x63\xa2\xaf\x5f\xd3\x55\x71\x9f\x4b\x06\xf6\xd5\x48\x68\ -\xb7\x0f\x3c\x26\x21\xbc\xcd\x85\xf2\x53\x7d\xeb\xcc\x77\x96\x42\ -\x5f\x1f\x60\xd3\x22\x74\x2d\x40\x54\x55\x15\x1d\xad\xcd\x68\x6d\ -\x6e\x40\x4b\x53\x03\xda\x5a\x1a\xd0\xda\x54\x8f\xce\x8e\x36\x50\ -\x38\x3c\x8f\x60\x9a\x3b\x53\xd7\x84\x17\x97\xac\x42\xd1\xa1\x93\ -\x7a\x87\xc2\xf4\xf3\xf3\x92\x3a\xc5\x55\x98\x67\xf8\x4f\xbd\x03\ -\x09\x05\x9c\x00\xf4\x62\xe1\x42\x92\x52\xa2\x51\xd2\x6e\xf3\x7d\ -\xf2\x07\x80\xca\x36\x81\x9c\x24\x6f\xcb\x59\x22\xa0\xd6\x2c\x50\ -\xd6\x28\xe0\xf4\xd3\x6a\x64\x66\x02\x61\x58\x9a\xef\x13\xa5\x4b\ -\x06\xaa\x3b\x05\xaa\xda\x05\x1c\x1a\x17\x40\x13\xa9\x68\x6f\x6d\ -\x46\x53\x5d\x0d\x1a\xeb\x6b\xd0\xd4\x50\x83\xe6\x86\x5a\x78\x3c\ -\x6e\x6d\x03\x61\x61\xc9\xe9\x72\x63\xf9\xa7\x5b\xb0\x6c\xd5\x66\ -\x5e\xee\x67\x00\xf0\x9b\x92\x7a\xc5\x51\x98\x6b\xf8\x5f\xbd\x03\ -\x09\x76\x9c\x00\xf4\x84\x48\xa4\xed\xc4\xae\x36\x1b\x26\xf5\xf5\ -\x4b\xad\x2e\x60\x63\xb9\x84\xf4\x38\x82\xd5\x25\xe0\xf0\xf3\xfb\ -\x92\x2f\x8f\x74\x55\x00\x2d\x56\x81\x5a\x13\x50\x6f\xd2\xee\x70\ -\x1e\xbb\xd5\x82\xda\x33\xa7\x50\x7f\xe6\x14\x1a\xeb\x6a\xd0\xdc\ -\x58\x0b\xb7\x3b\x48\x3a\x04\xb1\xb0\xb2\x69\xe7\x01\xbc\xf2\xf6\ -\xa7\x68\x69\x37\xe9\x1d\x0a\x0b\x26\x84\x3f\x94\xd4\x2a\xee\xc2\ -\x21\x86\x17\xf5\x0e\x25\x98\x71\x02\xd0\x83\xbf\xed\xa4\xaf\x5a\ -\xed\x62\x66\x7f\xbf\x5e\x51\x81\x16\x5b\x60\x9e\xb1\xb7\x58\x05\ -\x2c\x2e\x42\xd2\x05\x3b\xc5\x3c\x8a\xf7\x24\xbe\x06\x8b\xb7\x85\ -\xaf\x16\xcb\xfc\x56\x8b\x09\x75\x67\xaa\x50\x57\x7d\x0a\x75\x35\ -\xa7\xd0\xd4\x50\x1b\x1e\xdb\x0a\x58\xd0\x3a\x53\xdf\x8c\xbf\x2c\ -\x59\x85\x7d\x07\x4f\xe8\x1d\x0a\x0b\x56\x02\x7f\x3a\x50\x2f\x5b\ -\xa6\xe5\x1a\x17\xe9\x1d\x4a\xb0\xe2\x04\xa0\x1b\xaf\xec\xa6\x8d\ -\x4d\x96\x9e\xf7\xf9\xeb\x49\x56\x81\x2f\x2b\x25\x64\x24\x7a\x9b\ -\x0d\x39\x3d\x80\xc3\xe3\x6d\xc5\x1b\xe8\xb9\xd7\x6e\xb5\xe0\x74\ -\xe5\x71\x54\x55\x1c\x47\xcd\xe9\x72\x58\xcd\x7c\xf7\xc5\xb4\x61\ -\xb7\x3b\xb1\x64\xc5\x3a\xac\xfc\x62\x07\x64\x25\x04\xb6\xae\x30\ -\x3d\x09\x22\xf1\x5a\x71\xad\x6c\xe2\x66\x41\x5d\xe3\x04\xa0\x0b\ -\xaf\xec\xa2\x8f\x9a\x2c\xb8\x5e\xef\x38\x7a\xa3\x10\xd0\x64\x09\ -\x7c\x15\xbf\xaa\xaa\x68\x69\xac\x43\xe5\xc9\x23\xa8\x3c\x51\x86\ -\xe6\x86\x5a\x2e\xd4\x63\x9a\xdb\x59\x54\x86\x3f\x2f\xfe\x18\xcd\ -\xad\x9d\xbd\xbf\x98\x31\x2f\x83\x90\xc4\xb2\xe2\x3a\xd9\x31\x3d\ -\xcf\xf8\xb9\xde\xc1\x04\x1b\x4e\x00\x2e\xf0\xf7\x5d\xca\xe2\x26\ -\x2b\xee\xd5\x3b\x0e\xbd\x39\xec\x56\x54\x1c\x3b\x8c\x8a\xe3\x87\ -\x71\xe6\x74\x39\x3c\x6e\x2e\xd8\x63\xfa\xa8\xa8\xae\xc3\x8b\x8b\ -\x57\xe1\xd0\xb1\x53\x7a\x87\xc2\x42\x11\x21\x4a\x40\x7c\x5c\x5c\ -\xef\xb9\x6b\x7a\x6e\xd4\x3a\xbd\xc3\x09\x26\x9c\x00\x9c\xe3\x95\ -\x3d\xca\x1f\x9a\xcd\xd2\x53\x7a\xc7\xa1\x17\xb3\xa9\x03\xa7\x2b\ -\x8e\xa1\xf2\xf8\x11\x54\x55\x1c\x83\xaa\x06\xb8\x53\x11\x63\x3d\ -\xb0\xda\x1d\x58\xf2\xc1\x3a\xac\x5a\xbf\x03\x8a\xc2\xbf\x8b\x6c\ -\x40\xa2\x05\x49\x2b\x4b\xea\xe9\x96\xc2\x5c\xb1\x4d\xef\x60\x82\ -\x05\x27\x00\x5f\x5b\xbc\x87\x7e\x5c\x6b\xc6\x2f\x22\x6d\x61\xbb\ -\xa3\xad\x05\x27\x8e\x1c\x40\xf9\xf1\x43\x68\xaa\xaf\xd1\x3b\x1c\ -\xc6\x40\x44\xf8\xe2\xcb\xfd\x78\x6d\xd9\x1a\x9f\xba\x20\x32\xe6\ -\xa3\x78\x40\xfd\xac\xa8\x9e\xae\x9d\x91\x2b\x4a\xf4\x0e\x26\x18\ -\x70\x02\x00\xe0\xf5\xfd\x74\x57\xbd\x09\x2f\x45\xca\x3d\x86\xcb\ -\x69\xc7\x89\xb2\x52\x94\x95\xee\x47\x5d\x4d\x15\x57\xec\xb3\xa0\ -\x71\xb2\xaa\x16\x2f\x2d\x5e\x85\xc3\x27\xaa\xf4\x0e\x85\x85\x23\ -\x42\x8a\x04\x75\x7d\x71\x0d\x5d\x3e\x3d\x5f\x54\xe8\x1d\x8e\xde\ -\x22\x3e\x01\x78\x7d\x2f\xcd\x68\x35\x63\xa5\xac\x86\xf7\x09\xb2\ -\x8a\x22\xe3\x74\xc5\x71\x1c\x3d\xb8\x1f\x15\xc7\x0f\x43\xe1\x0a\ -\x6a\x16\x44\x2c\x36\x3b\xde\xfc\x70\x3d\x56\xae\xdb\xc1\x8f\x9e\ -\x58\xa0\x65\x08\x49\xfd\xec\x50\x35\xcd\x99\x3c\x54\x44\x74\xbf\ -\xe8\x88\x4e\x00\x5e\xd9\x47\xf9\xed\x36\x6c\x73\x2b\xe8\xe2\x98\ -\x9c\xf0\xd0\xdc\x58\x87\x83\xfb\x77\xe2\xf8\x91\x12\xee\xa5\xcf\ -\x82\x8e\xaa\x12\xd6\x6c\xde\x83\xd7\x97\xff\x13\x66\x8b\x4d\xef\ -\x70\x58\xe4\x18\x2f\x1b\x95\xd5\xe5\xe5\x74\xe3\xe8\xd1\x22\x62\ -\xbb\x94\x45\x6c\x02\xb0\x70\x2d\x25\x5b\xed\x28\x75\x7a\x10\xa7\ -\x77\x2c\xfe\xa6\x28\x0a\x2a\x8e\x1f\xc6\xa1\xe2\x5d\xa8\xae\xe4\ -\x46\x29\x2c\x38\x9d\x3c\x55\x8b\x3f\x2f\x5a\x89\xb2\xf2\xd3\x7a\ -\x87\xc2\x22\x92\xb8\xca\x12\xaf\xbe\x45\x44\x0f\x0a\x21\x22\xf2\ -\x39\x68\x44\x26\x00\xf7\xad\x20\x43\x42\x22\x4a\x2c\x4e\xa4\xeb\ -\x1d\x8b\x3f\x59\xcd\x26\x1c\x2a\xd9\x85\x03\x7b\x77\xc0\x61\xb7\ -\xea\x1d\x0e\x63\x5d\x32\x5b\xed\x58\xba\x82\x97\xfb\x59\x50\xf8\ -\x4e\x49\xbd\x7a\x02\xc0\x42\xbd\x03\xd1\x43\x44\x26\x00\x73\x72\ -\x68\x7d\xbb\x43\x8c\xd4\x3b\x0e\x7f\xa9\xa9\xaa\x40\xd1\xae\x2d\ -\x38\x55\x7e\x94\x1b\xf4\xb0\xa0\xa5\xaa\x2a\x56\x6f\xdc\x8d\x45\ -\xcb\xd7\xc2\x62\xf3\xc3\x91\x98\x8c\xf9\x81\x00\x7e\x73\xa0\x56\ -\xae\x9a\x36\xc4\xf8\xb6\xde\xb1\x68\x2d\xe2\x12\x80\x57\x77\xd1\ -\xff\x35\x5a\x71\x9d\xde\x71\x0c\x18\x11\x2a\x4f\x96\x61\xdf\xf6\ -\x4d\xde\x4a\x7e\xc6\x82\xd8\xf1\xca\x33\x78\x71\xd1\x2a\x1c\xad\ -\xa8\xd6\x3b\x14\xc6\x2e\x24\x48\x12\x8b\x4a\xea\x3c\x75\x85\x79\ -\x51\x9b\xf4\x0e\x46\x4b\x11\x95\x00\xbc\xb6\x87\x9e\x6c\x32\xe3\ -\xdf\xf4\x8e\x63\x20\x54\x45\xc1\xf1\xc3\x25\xd8\xbb\x63\x13\xda\ -\x5a\x1a\xf5\x0e\x87\xb1\x1e\x99\x2d\x36\x2c\xfd\x68\x03\x56\xae\ -\xdb\x0e\x55\xe5\xd5\x29\x16\xa4\x08\x51\x80\xb4\x72\x4f\x0d\x5d\ -\x39\x2b\x5f\x1c\xd2\x3b\x1c\xad\x44\x4c\x02\xf0\xc6\x1e\xba\xa2\ -\xd9\x8a\x45\xa1\xfa\xc4\x51\xf6\x78\x70\xa8\x64\x17\x8a\x76\x6e\ -\x85\xd9\x14\xd1\x3b\x57\x58\x08\x50\x14\x15\x2b\xd7\xed\xc0\x92\ -\x0f\xd7\xc1\x66\xe7\xdd\x27\x2c\x24\x24\x1f\x6f\xa6\x6d\x6f\x14\ -\xd1\x4d\xcf\xce\x10\x7b\xf5\x0e\x46\x0b\x11\x91\x00\xbc\xb1\x8d\ -\x72\x3a\x1c\xd8\xe8\x51\x21\xe9\x1d\x4b\x5f\x11\x11\x4e\x1e\x3d\ -\x88\x6d\x1b\x3f\x83\xa9\xa3\x4d\xef\x70\x18\xeb\x55\xe9\xd1\x4a\ -\xbc\xb4\x64\x15\x2a\xab\xeb\xf5\x0e\x85\xb1\x3e\x69\x30\x8b\x14\ -\x83\x84\xcd\x4b\xb7\xd2\x90\x27\xae\x11\x61\x7f\xea\x54\xd8\x27\ -\x00\x0b\xb7\x92\xd1\x02\x2a\xb2\x7b\x44\xac\xde\xb1\xf4\x55\x75\ -\xe5\x09\x6c\x5d\xbf\x1a\xad\x4d\xfc\x46\xca\x82\x5f\x5b\xa7\x19\ -\xaf\xbe\xfb\x39\x36\x6c\x2b\xe6\x62\x54\x16\x72\x08\x80\x4b\x06\ -\x54\x42\x42\x5c\x02\xf6\x02\x18\xab\x77\x4c\x81\x16\xf6\x09\xc0\ -\xa0\x18\xac\x69\xb1\x8a\x5c\xbd\xe3\xe8\x8b\xba\x33\xa7\xb0\x7d\ -\xd3\xe7\xa8\xad\xe6\xd3\xcf\x58\xf0\x3b\xbb\xdc\xbf\xf8\xc3\x2f\ -\x60\xb7\x3b\xf5\x0e\x87\xb1\x7e\x11\x00\x06\xc5\x13\x5a\x6c\x02\ -\x2d\x36\x8c\x79\x63\x2f\xbd\xf7\xec\x4c\xf1\x90\xde\x71\x05\x52\ -\x58\x27\x00\xaf\xec\xa4\x9f\x35\x5b\x71\x93\xde\x71\xf8\xaa\xb5\ -\xa9\x1e\x5f\xae\xff\x14\xa7\x2b\x8f\xeb\x1d\x0a\x63\x3e\x29\x3e\ -\x5c\x8e\x97\xde\x5c\x85\xd3\x35\x5c\x90\xca\x42\xdf\x98\x4c\xa0\ -\xe5\xeb\x86\x94\x0d\x26\x3c\xf8\x66\x11\x6d\x7d\x72\x86\x58\xac\ -\x6f\x54\x81\x13\xb6\x09\xc0\x2b\xbb\x68\x76\xab\x0d\x7f\x08\x85\ -\x85\x48\x8f\xdb\x8d\xfd\xbb\x36\x63\xef\xb6\x8d\xdc\xa3\x9f\x85\ -\x84\xd6\x0e\x13\x5e\x5b\xb6\x86\x97\xfb\x59\x58\xc9\x4c\x20\x64\ -\x27\x01\x8d\x16\x01\x15\x40\xa3\x19\xaf\xff\x63\x1f\xed\xfe\xee\ -\x65\xa2\x4c\xef\xd8\x02\x21\x2c\x13\x80\x85\xbb\x28\xdd\xe2\xc2\ -\x06\x85\x82\xbf\xe8\xaf\xf2\xc4\x11\x6c\xfa\xe7\x47\xb0\x98\xc2\ -\xbe\xde\x84\x85\x01\x59\x51\xb0\x6a\xdd\x4e\x2c\xfa\x60\x2d\x1c\ -\x8e\x88\x6d\xa1\xce\xc2\xd8\xf8\x2c\x42\xa3\xc5\x7b\x36\x9c\x53\ -\x86\xc1\xe2\xc4\x8e\x85\x55\x94\xb3\x70\xb8\x08\xbb\xe7\x5b\xe1\ -\x97\x00\x10\x89\xa4\x6d\xb4\xd7\xe4\x11\x09\x7a\x87\xd2\x93\xf6\ -\xd6\x66\x6c\x59\xbb\x92\x97\xfb\x59\xc8\x28\x39\x52\x8e\x17\x97\ -\xf0\x72\x3f\x0b\x6f\x29\xb1\x40\x72\x2c\x60\xfe\x7a\xba\x37\x39\ -\x91\x9a\xd3\x84\x2d\x00\x66\xeb\x1a\x58\x00\x84\x5d\x02\xf0\xb7\ -\x5d\xea\xbb\xad\x2e\x69\x94\xde\x71\x74\xc7\xe3\x71\x63\xe7\x96\ -\xb5\x28\xd9\xbb\x0d\x2a\x2f\xf7\xb3\x10\xd0\xd8\xd2\x8e\xbf\xbd\ -\xb5\x1a\xdb\xf6\x1e\xd6\x3b\x14\xc6\x34\x91\x10\x4d\x30\x3b\xbf\ -\x3d\x21\xbe\xd1\x82\xcb\xdf\x2b\x51\x7e\xfb\x50\xa1\xe1\x37\x3a\ -\x86\xe5\x77\x61\x95\x00\xbc\xbe\x87\x1e\x6e\xb0\x20\x68\xab\x36\ -\x1b\x6a\xab\xb1\x76\xd5\x32\x74\xb4\x35\xeb\x1d\x0a\x63\xbd\x3a\ -\xbb\xdc\xbf\xf8\xfd\xb5\xb0\x3b\x79\xb9\x9f\x45\x0e\xf9\x82\x7b\ -\x33\x22\xa0\xba\x53\xfa\xf5\xea\x13\xb4\x71\xde\x58\xb1\x5d\x9f\ -\xa8\xfc\x2f\x6c\x12\x80\xd7\x0e\xd0\xb0\xb6\x0e\x5a\x4a\x24\x7a\ -\x7f\xb1\xc6\x14\x45\xc1\xae\xad\x5f\x60\xdf\x8e\xcd\x20\x0a\xd5\ -\x5e\x84\x25\xdb\xf2\xe7\x00\x00\x20\x00\x49\x44\x41\x54\x2c\x92\ -\xd4\x37\xb5\xe1\x85\xdf\xbe\x8e\xba\xa6\x56\xbd\x43\x61\x4c\x73\ -\x2e\xf9\xe2\x79\xc4\x29\x43\x9c\x6e\xa6\x2f\xf6\x94\x53\xee\xac\ -\xd1\xc2\xac\x43\x58\x7e\x17\x1e\x09\x00\x91\x70\xee\xa0\xed\x2e\ -\x59\x04\xdd\x7f\x4f\x6b\x73\x03\xd6\xae\x5a\x86\xe6\x86\x5a\xbd\ -\x43\x61\xcc\x67\x4d\xad\x1d\x3c\xf9\xb3\x88\xa5\x74\xb3\xb1\xa5\ -\xdd\x21\x12\x8e\x76\xd2\x66\x00\x97\x6a\x1a\x50\x80\x04\x7d\x95\ -\xbc\x2f\x5e\xdd\xa3\x2e\xe9\x70\x88\x21\x7a\xc7\x71\x2e\x22\x15\ -\xfb\x76\x6c\xc2\xbb\xaf\xff\x89\x27\x7f\xc6\x18\x0b\x21\xe9\xf1\ -\xdd\x6f\x6d\xad\xee\x14\x33\x3e\x2c\x55\xfe\x43\xc3\x70\x02\x26\ -\xe8\xee\x98\xfb\xea\xd5\x1d\x74\x4b\xb3\x95\x9e\xd0\x3b\x8e\x73\ -\x59\xcd\x26\x7c\xfe\xd1\x5b\xa8\x3b\xc3\x9d\xfc\x18\x63\x2c\xd4\ -\x4c\xca\x26\x38\x3d\x40\x8b\xed\xdb\x47\x01\x51\x12\x90\x18\xe3\ -\x2d\x10\x54\x20\x7e\x55\x54\x4f\x6f\xcf\xc8\x15\x67\x74\x0c\x73\ -\xc0\x42\x3a\x01\xf8\xdf\x8d\x94\x62\xf2\xd0\x4a\x35\x88\x9e\xfb\ -\xd7\x54\x57\x62\xcd\x47\x6f\xc1\x66\x09\x8b\x47\x44\x8c\x31\x16\ -\x71\xa2\x0d\xc0\x9c\x61\x04\x97\x4c\xf0\xa8\x80\x51\x02\x62\xcf\ -\x9f\x2d\x63\x41\xea\xbb\x2b\x88\xae\x5d\x20\x44\xc8\x6e\xe7\x0a\ -\xe9\x47\x00\xb1\x71\xd8\xea\xf0\x88\x38\xbd\xe3\x00\x00\x10\xa1\ -\x78\xcf\x57\xf8\xe8\xed\x57\x78\xf2\x67\x8c\xb1\x30\x10\x63\x04\ -\x12\xa3\x2f\x9a\xfc\xbf\x26\xae\x1a\x5d\xaf\xfe\x54\xeb\x98\xfc\ -\x29\x64\x13\x80\xd7\xf6\xd0\xbf\xb4\xdb\x31\x4d\xef\x38\x00\xc0\ -\xe3\x76\xe1\xf3\x15\x6f\x61\xeb\x17\xab\x78\x6f\x3f\x63\x8c\x45\ -\x08\x12\xf8\xfd\x81\x06\x0a\xd9\x82\xc0\x90\x4c\x00\xfe\xbe\x97\ -\x86\xb7\x59\xf1\xff\xf4\x8e\x03\x00\xda\x5b\x9a\xf0\xee\x3f\xfe\ -\x8c\x13\x47\x4b\xf5\x0e\x85\x31\xc6\x98\x96\x08\x51\xa4\xaa\x6f\ -\x17\xd5\x53\xbc\xde\xa1\xf4\x47\x48\x26\x00\xb2\x07\x5b\xdc\x2a\ -\x0c\x7a\xc7\x51\x7e\xfc\x30\xde\x7d\xe3\x4f\x68\x6f\x69\xd2\x3b\ -\x14\xc6\x18\x63\xfa\x18\x2f\x41\xfd\x93\xde\x41\xf4\x47\xc8\x25\ -\x00\x6f\xec\xa6\xbf\x77\xd8\x31\x4c\xef\x38\x0e\x17\xef\xc6\xe7\ -\x1f\xbe\x09\x8f\xc7\xad\x77\x28\x8c\x31\xc6\xf4\x44\xf8\x5e\x49\ -\x9d\x7c\xa7\xde\x61\xf4\x55\x48\x25\x00\x4b\xf7\xd0\xac\x26\x3b\ -\x7e\xa0\x6b\x10\x44\xd8\xf5\xe5\x17\x58\xff\xd9\x07\x50\x55\xee\ -\xea\xc7\x18\x63\x0c\x00\xc4\xe2\x7d\xcd\x94\xad\x77\x14\x7d\x11\ -\x32\x09\xc0\xc2\x85\x24\xb5\xbb\xf1\xb9\xac\x40\xb7\x3d\x7f\x8a\ -\xa2\x60\xcd\xaa\x77\xb1\x6b\xeb\x3a\xbd\x42\x60\x8c\x31\x16\x9c\ -\x32\xa3\x3c\xb4\x48\xef\x20\xfa\x22\x64\x12\x80\xc1\x37\x61\x89\ -\xc9\x89\x0c\xbd\xc6\xf7\xb8\x5d\xf8\xe4\xbd\x7f\xe0\xf8\xa1\x62\ -\xbd\x42\x60\x8c\x31\x16\xc4\x08\x74\xfb\xfa\x93\xca\x8f\xf4\x8e\ -\xc3\x57\x21\x91\x00\x2c\xda\x43\x93\x5b\xed\x78\x4c\xaf\xf1\xad\ -\x16\x13\xde\x5b\xf4\x12\x4e\x57\x1e\xd7\x2b\x04\xc6\x18\x63\x21\ -\xe0\x70\xbd\x78\xe9\xbd\x03\x34\x55\xef\x38\x7c\x11\x12\x09\x80\ -\xc5\x8d\x7f\x7a\x74\x5a\xfa\xb7\x5b\x2d\xf8\xe8\x9d\x57\xd1\xda\ -\xdc\xa0\xc7\xf0\x8c\x31\xc6\x42\x44\x87\x03\x30\xbb\x85\xa1\xc5\ -\x8e\x35\x7a\xc7\xe2\x8b\xa0\x4f\x00\x5e\xdf\xad\xfc\xae\xd3\x09\ -\x5d\x0e\xfa\xb1\xdb\x2c\xf8\xf0\xed\x97\xd1\xd6\xdc\xa8\xc7\xf0\ -\x8c\x31\xc6\x42\xc8\xa9\x76\xef\x7d\x6a\xbb\x0d\x79\x6f\x15\x29\ -\x7f\xd4\x39\x9c\x5e\x05\x75\x02\xf0\xda\x2e\xca\x6b\xb5\x4b\xbf\ -\xd4\x63\x6c\xbb\xcd\x82\x0f\x97\xf2\xe4\xcf\x18\x63\xac\x77\x76\ -\x37\x50\x67\xfa\x76\xa1\xba\xce\x24\x7e\xfa\xfe\x01\x1a\xa6\x5f\ -\x44\xbd\x0b\xea\x04\x40\x56\x69\xbd\x5b\xd1\xbe\xe1\xcf\x37\x93\ -\x7f\x0b\x4f\xfe\x8c\x31\xc6\x7a\x57\xd6\x24\xa0\x9e\x73\x8a\xb0\ -\x4b\x11\x52\x9b\x13\xff\xd4\x2f\xa2\xde\x05\x6d\x02\xb0\x78\x2f\ -\x3d\xdd\x6a\x17\x13\xb5\x1e\xd7\x61\xb7\x62\xc5\x5b\xaf\xf0\xe4\ -\xcf\x18\x63\xcc\x27\xed\x0e\xa0\xde\x7c\x71\x99\x5a\x8b\x05\x13\ -\x96\x16\x51\xd0\xee\x0a\x08\xca\x04\xe0\x8f\x07\x29\xa1\xc5\x8e\ -\x97\xa9\xf7\x97\xfa\x95\xcb\x69\xc7\x87\x4b\x5f\xe6\x82\x3f\xc6\ -\x18\x63\x3e\x21\x02\x0e\x37\x48\xe8\x6a\xbe\x22\x00\xcd\x56\xfc\ -\x71\xc9\x0e\x4a\xd2\x3a\x2e\x5f\x04\x65\x02\x90\xe2\xc2\x27\x0e\ -\x0f\x62\xb4\x1c\x53\x55\x14\x7c\xfa\xe1\x52\x9e\xfc\x19\x63\x8c\ -\xf9\xec\x68\xb3\x40\x87\xa3\xfb\xcf\xdb\xdc\x88\xf1\x18\xb1\x52\ -\xbb\x88\x7c\x17\x74\x09\xc0\xf2\x52\xba\xa2\xc1\x8c\x1b\x34\x1d\ -\x94\x08\xeb\x3e\x7b\x1f\x67\x4e\x9d\xd4\x74\x58\xc6\x18\x63\xa1\ -\xab\xbc\x4d\xa0\xa2\xb5\xf7\x1d\xea\x4d\x16\xdc\xf0\xee\x01\x9a\ -\xad\x41\x48\x7d\x12\x74\x09\x40\xab\x15\x2b\x14\x8d\x5b\xec\xef\ -\xfa\x6a\x1d\x8e\x96\xee\xd7\x76\x50\xc6\x18\x63\x21\x49\x56\x81\ -\xd2\x7a\x81\xb2\x46\xd1\xe5\xd2\xff\x85\x14\x02\xda\xec\x58\x11\ -\xf0\xc0\xfa\x28\xa8\x12\x80\xb7\x8b\x94\x9f\xb7\xda\x91\xa3\xe5\ -\x98\xc7\x8f\x94\x60\xd7\x97\xeb\xb5\x1c\x92\x31\xc6\x58\x08\x52\ -\x01\x54\x77\x0a\x6c\xae\x90\x70\xba\xa3\x6f\xbd\xe9\xda\x6d\xc8\ -\x7b\xa7\x44\xf9\x45\x60\x22\xeb\x9f\xa0\x49\x00\xde\x39\x48\x09\ -\x8d\x56\xf1\x5b\x2d\xc7\xac\xa9\xae\xc4\x17\xab\xde\xf3\x56\x71\ -\x30\xc6\x18\x63\x5d\x70\xc9\xc0\x89\x16\x81\x8d\x27\x24\x1c\xa8\ -\x13\x70\x78\xfa\x77\x9d\x46\xb3\xb4\xf0\x8d\x22\x8a\xf7\x6f\x74\ -\xfd\x67\xd4\x3b\x80\xb3\x5c\x6e\xac\xb0\xb9\x45\xb4\x56\xe3\xb5\ -\xb7\x34\x61\xf5\xf2\xc5\x50\x14\x59\xab\x21\x19\x63\x8c\x85\x10\ -\xbb\x07\x28\x6f\x15\x38\xd3\x21\xa0\xf8\xe1\x3e\xd1\xea\x46\x4c\ -\xaa\x82\xe5\x00\xe6\x0d\xfc\x6a\x03\x17\x14\x2b\x00\x2b\xca\x68\ -\x6a\xa3\x19\xb7\x68\x35\x9e\xdb\xed\xc2\xea\x0f\x97\xc0\xe5\xb4\ -\x6b\x35\x24\x63\x8c\xb1\x10\xe1\x92\x81\x92\x3a\x81\x4d\x27\x25\ -\x54\xb5\xfb\x67\xf2\x3f\xab\xc9\x8a\x3b\x97\x1d\xa2\x49\xfe\xbb\ -\x62\xff\x05\xc5\x0a\x40\xab\x05\x1f\x68\x76\xd8\x0f\x11\xd6\x7f\ -\xb2\x1c\xed\x2d\x4d\x9a\x0c\xc7\x18\x63\x2c\x74\xb4\xd8\x04\xf6\ -\xd7\x08\xb8\x95\xc0\x5c\xdf\xa3\x40\x74\x58\xf0\x31\x80\xb1\x81\ -\x19\xc1\x77\xba\xaf\x00\x2c\x3f\x40\xf3\x9a\x2c\xda\x7d\x23\xf6\ -\xef\xda\x82\x13\x47\x4b\xb5\x1a\x8e\x31\xc6\x58\x88\x68\xb6\x0a\ -\xec\x3e\x1d\xb8\xc9\xff\xac\x56\x1b\xc6\xbc\xb9\x8f\xee\x0f\xec\ -\x28\xbd\xd3\x7d\x05\xa0\xc5\x8e\x7f\x68\x55\x83\x57\x77\xe6\x14\ -\xb6\x6f\x0a\x89\x53\x1a\xd9\x05\x24\x49\x60\x50\x6a\x32\x72\xb2\ -\x06\x21\x2d\x35\x11\xc9\x89\xf1\x48\x4a\x8c\x47\x72\x42\x3c\x84\ -\x00\x0c\x06\x03\x14\xc5\xfb\x57\x5b\x79\xa6\x01\x1b\xb6\x15\xeb\ -\x1c\x31\x63\x2c\x94\x58\x5d\xc0\xfe\x1a\x01\x2d\x76\xa1\x13\x80\ -\x0e\x27\x5e\x05\xf0\xa1\x06\xc3\x75\x4b\xd7\x04\x60\x59\x89\xf2\ -\x8b\x93\xad\xc8\xd4\x62\x2c\xb7\xd3\x81\xb5\x2b\xdf\x85\xaa\x6a\ -\xdc\x64\x80\xf5\x89\x10\x02\xf9\x39\x99\x18\x3b\x72\x08\x46\xe4\ -\xe7\x60\x58\x7e\x36\x86\xe7\x67\x63\x70\x46\x1a\xa2\x8c\xbe\x9d\ -\x0b\xb5\xff\xe0\x49\x4e\x00\x18\x63\x3e\x53\x09\xd8\x73\x46\x82\ -\x47\xc3\xe9\xc1\xe4\x44\xfa\xd2\x62\xfa\xd5\x13\xd3\xc5\xff\x68\ -\x37\xea\xf9\x74\x4b\x00\x16\x12\x19\x1b\xb7\xe3\x37\x5a\x8d\xb7\ -\xe1\xf3\x15\x30\x75\xb6\x6b\x35\x1c\xf3\x91\xc1\x20\x61\xc2\xa8\ -\xa1\x28\xbc\x64\x34\xa6\x8c\x1f\x81\xf1\xa3\xf3\x91\x94\x10\x34\ -\xbb\x64\x18\x63\x11\xe0\x78\x8b\x80\xd5\xad\xfd\xb8\x2d\x16\xfc\ -\xc7\x8a\x32\xfa\xd3\x82\x89\x42\x87\xd1\x75\x4c\x00\x86\x15\xab\ -\xaf\x9d\x72\x4a\x71\x5a\x8c\x55\x56\xba\x0f\xc7\x8f\x94\x68\x31\ -\x14\xf3\x41\x5a\x4a\x12\xae\xb8\x74\x22\xe6\x4c\xbf\x04\x85\x97\ -\x8c\x42\x7c\x9c\xa6\xc7\x3e\x30\xc6\xd8\x37\xcc\x4e\xa0\xa2\x45\ -\x9b\x1a\xf4\x0b\x59\xdd\x88\xb5\x39\xd4\x97\x01\x7c\x57\x8f\xf1\ -\x75\x49\x00\x96\xed\xa1\xe4\x5a\x0b\x1e\xd7\x62\x2c\x8b\xb9\x13\ -\x9b\xbf\x58\xa5\xc5\x50\xac\x07\xe9\xa9\x49\xb8\x6e\xf6\x34\x5c\ -\x33\x7b\x0a\x2e\x19\x33\x1c\x92\xa4\xcf\x1f\x1c\x63\x8c\x9d\xeb\ -\x50\xa3\x36\xcf\xfd\xbb\xd3\x68\x91\x9e\x58\xb6\x87\xfe\xf5\xe1\ -\x59\xc2\xac\xf5\xd8\xba\x24\x00\x2e\x89\xde\xb2\x7b\x84\x26\x63\ -\x6f\xf8\xec\x43\xb8\x9d\x3d\x1c\xd5\xc4\x02\x26\xda\x68\xc4\xd5\ -\xb3\x26\xe3\xe6\xab\x2f\xc5\x8c\xc9\x63\x60\x30\xe8\xbe\xe9\x84\ -\x31\xc6\xbe\xd1\x60\x16\x68\xb5\xe9\x7b\x33\xe2\xf0\xc0\x68\x8f\ -\xc7\x5b\x00\xe6\x6b\x3d\xb6\xe6\x09\xc0\x8a\x72\xca\xac\xa8\xa1\ -\xbb\xb4\x18\xeb\x70\xc9\x1e\x54\x95\x1f\xd5\x62\x28\x76\x8e\xcc\ -\xf4\x14\xdc\x71\xfd\x2c\xdc\x7d\xd3\x1c\xa4\xa5\x04\xe5\x31\xd8\ -\x8c\xb1\x08\xa7\x10\x70\xa4\x29\x38\x56\x22\x9b\xad\x34\xef\x8d\ -\x22\x2a\x78\x76\x86\x38\xa3\xe5\xb8\x9a\x27\x00\x56\x13\xde\x75\ -\xca\x22\xe0\xb7\x82\x36\xab\x19\x5b\xd7\xaf\x0e\xf4\x30\xec\x1c\ -\x13\x47\x0f\xc3\x43\xf3\xae\xc5\x15\x97\x5e\xc2\x4b\xfc\x8c\xb1\ -\xa0\x56\xd9\x2a\x60\xd3\xa5\xf4\xee\x62\x6e\x45\x08\xd5\x43\x6f\ -\x03\xb8\x46\xcb\x71\x35\x4d\x00\x56\x1f\xa6\xfc\xb2\x66\xdc\xa8\ -\xc5\x58\x5b\xbf\xf8\x84\x97\xfe\x35\x32\xab\x70\x3c\x1e\x9e\x77\ -\x1d\xa6\x4e\x18\xa9\x77\x28\x8c\x31\xd6\x2b\x97\x0c\x9c\x6c\x0d\ -\xae\x9b\x94\x56\xbb\xb8\xfa\x9d\xbd\x34\xfc\xd1\x99\xa2\x4a\xab\ -\x31\x35\x4d\x00\xda\xec\x78\xdb\xa5\x41\xcb\xdf\xea\xca\x13\x5c\ -\xf5\xaf\x81\x4b\xc6\x0c\xc3\xb3\x0f\xdd\x86\x69\x13\x47\xe9\x1d\ -\x0a\x63\x8c\xf9\xec\x74\x87\x80\x1c\x64\x2d\x61\x3c\x0a\x84\x03\ -\x58\x0a\x60\xae\x56\x63\x6a\x96\x00\xbc\x7f\x98\xf2\x4f\xb5\x04\ -\xfe\x3f\x4c\x51\x64\x6c\xfa\xe7\x47\x81\x1e\x26\xa2\x8d\x28\xc8\ -\xc1\xf7\x1f\xbe\x03\xb3\x0a\xc7\xeb\x1d\x0a\x63\x8c\xf5\x09\x11\ -\x50\xdd\x11\x5c\x77\xff\x67\x35\x5b\x71\x95\x96\xab\x00\x9a\x25\ -\x00\x56\x07\xde\x71\xc9\x81\xbf\xfb\x2f\xda\xb9\x15\x1d\x6d\x2d\ -\x81\x1e\x26\x22\x25\x25\xc4\xe3\xa9\xfb\x6f\xc6\xdd\x37\xcd\xe1\ -\x8a\x7e\xc6\x58\x48\xea\x70\x7a\x8f\xf9\x0d\x46\x1e\x05\xc2\x29\ -\xf0\x26\x34\xaa\x05\xd0\x24\x01\x58\x79\x90\x86\x1c\x6f\xc5\xd5\ -\x81\x1e\xc7\x66\x35\x63\xcf\xf6\x8d\x81\x1e\x26\xe2\x08\x21\x70\ -\xfb\x75\x33\xf1\xdc\x83\xb7\x23\x25\x39\x41\xef\x70\x18\x63\xac\ -\xdf\x3a\x1d\xc1\x79\xf7\x7f\x56\x93\x05\x57\xbf\x57\x42\x43\x1f\ -\x2a\x14\xd5\x81\x1e\x4b\x93\x04\xa0\xd3\x83\x25\x5a\x3c\xfb\xdf\ -\xbe\x69\x0d\x3c\x6e\x57\xa0\x87\x89\x28\xb9\x83\x07\xe1\x67\xcf\ -\x2e\xc0\x8c\xc9\x63\xf4\x0e\x85\x31\xc6\x06\xcc\x25\xeb\x1d\x41\ -\xcf\x3c\x0a\x84\xd5\x83\xa5\x00\xae\x0d\xf4\x58\x01\x5f\xc7\x5d\ -\x55\x4b\x83\x9a\xcd\xb8\x3e\xd0\xe3\x34\x35\xd4\xa2\xac\x74\x5f\ -\xa0\x87\x89\x18\x42\x08\x2c\xb8\xed\x2a\xbc\xfd\xe7\x9f\xf1\xe4\ -\xcf\x18\x0b\x1b\x5a\x9d\x3e\x3b\x10\xcd\x56\xcc\x7d\xbb\x94\xf2\ -\x02\x3d\x4e\xc0\x57\x00\x2c\xcd\x78\xcd\x21\x07\x3e\xd1\xd8\xb6\ -\xf1\x33\x50\x28\xfc\x64\x43\x40\x5a\x4a\x12\xfe\xfd\x07\x0f\x70\ -\x91\x1f\x63\x2c\xec\x44\xf9\x76\xa8\xa8\xae\x3c\x0a\x84\xdd\x41\ -\xaf\x03\xb8\x23\x90\xe3\x04\x74\x62\xde\xb5\x8b\xe2\x9a\xcc\x74\ -\x77\x20\xc7\x00\x80\xda\xea\x53\xa8\xae\x3c\x11\xe8\x61\x22\xc2\ -\x8c\xc9\x63\xf0\xd6\x9f\xff\x95\x27\x7f\xc6\x58\x58\x8a\x8f\xd2\ -\x3b\x02\xdf\xb4\xdb\xc5\x2d\x0b\x8b\x28\xa0\x47\xa3\x06\x74\x05\ -\xa0\x32\x56\xfd\xb3\xd5\x2a\x05\x74\x0c\x49\x00\x25\xdb\x3f\x0f\ -\xe4\x10\x11\x41\x92\x04\x9e\xbe\xff\x56\x3c\x32\xff\x3a\x08\x11\ -\xdc\x45\x32\x8c\x31\xd6\x5f\xc1\xba\x03\xe0\x42\x2e\x05\x86\x3c\ -\x8f\xfa\x27\x00\xdf\x0f\xd4\x18\x01\x5b\x01\x58\x41\x64\x68\xb6\ -\x8a\x27\x02\x75\x7d\x00\x88\x35\x02\x71\xe6\x32\x9c\x2c\x3f\x15\ -\xc8\x61\xc2\x5e\x42\x7c\x1c\xfe\xf7\x17\x4f\xe3\xd1\x7b\xae\xe7\ -\xc9\x9f\x31\x16\xb6\x5c\x32\x70\x52\xa7\xa3\x7f\xfb\xa3\xd3\x2d\ -\x3d\x0e\xa2\x80\x05\x1c\xb0\x04\x20\xe1\x24\x9e\x4b\x8c\x41\xec\ -\x88\x74\xc2\xb0\x34\x42\x4e\x32\x21\x31\x1a\x7e\xdb\x0a\x90\x18\ -\x0d\x5c\x35\x5c\xc5\xc7\x9f\x6e\xf0\xd3\x15\x23\x53\x41\x6e\x16\ -\x16\xfd\xe1\xc7\x98\x5d\x38\x41\xef\x50\x18\x63\x2c\xa0\xca\x1a\ -\x05\x3c\x41\xd6\x01\xb0\x27\x36\x17\xe2\x16\xed\xc3\xbf\x06\xea\ -\xfa\x01\x5b\x9e\xcf\x49\x54\x1e\xca\x49\xbc\x70\xba\x27\xb8\x15\ -\xa0\xcd\x2e\xd0\x6a\x03\x5a\x6d\x02\x66\x27\xd0\xd7\xd2\xbd\xb4\ -\x38\x60\x56\x81\x8a\xb2\xe3\xe5\x28\x2b\x3f\xed\xa7\x88\x23\xcf\ -\x84\xd1\x43\xf1\xc7\x5f\x3e\xc3\x7b\xfb\x19\x63\x61\xaf\xde\x2c\ -\x70\xc6\x14\x3a\x77\xff\x67\x99\xdd\xf8\x37\x00\x7f\x0c\xc4\xb5\ -\x03\x92\x00\x14\xd7\xd3\x74\x90\x7a\x79\x57\x9f\x8b\x36\x00\x39\ -\x49\x84\x9c\x24\x00\x20\x38\x65\xa0\xc1\x22\x50\xdd\x2e\xd0\xe9\ -\xec\xfd\xda\xc3\xd3\x08\x97\xe4\x10\x0c\x02\x78\x67\xd5\x26\xff\ -\x06\x1e\x41\xae\xbc\xf4\x12\x2c\x7c\xe1\x51\xc4\x44\x87\x48\x45\ -\x0c\x63\x8c\xf5\x93\xc5\x05\x94\xd6\x87\xde\xe4\x0f\x00\x26\x07\ -\x32\x97\xee\xa3\xf9\x4f\x5c\x26\x56\xf9\xfb\xda\x01\x49\x00\x04\ -\xa9\x3f\xf5\xf5\xb5\xb1\x46\xef\xa4\x3e\x3c\x8d\xd0\x62\x13\xa8\ -\x6a\x07\x1a\xcd\x02\xe7\xae\xd2\x18\x04\x30\x28\x81\x30\x36\x93\ -\x30\xe8\xeb\x9a\xc8\xa3\x15\xd5\x28\x3a\x74\xd2\xcf\x91\x47\x86\ -\xbb\x6e\x98\x8d\x7f\x79\xe6\x1e\x48\x12\xb7\xf3\x65\x8c\x85\x37\ -\x97\x0c\xec\xa9\x96\xe0\x56\xf4\x8e\xa4\xff\xcc\x1e\xfa\x5f\x00\ -\xc1\x9f\x00\x94\x9c\xa1\x5c\x40\xbd\xb7\x3f\x5f\x9b\x99\x40\xc8\ -\x4c\x00\x14\x22\x98\x9c\x80\xa2\x0a\x44\x49\x84\xa4\x18\xe0\xc2\ -\xd6\xf3\x1f\x7c\xba\xd5\x1f\xe1\x46\x9c\xfb\x6e\xbd\x0a\xcf\x3f\ -\x31\x8f\x8b\xfd\x18\x63\x61\xcf\x25\x03\x3b\xab\x25\xd8\x42\xa4\ -\xf2\xbf\x3b\x1d\x36\x31\xfa\x1f\xfb\x68\xea\x77\x2f\x13\xa5\xfe\ -\xbc\xae\xdf\x13\x00\x61\x54\xbf\x4f\x84\xe8\x81\x5c\xc3\x20\x80\ -\xf4\x38\xa0\xbb\xea\x80\x96\x76\x13\xb6\xed\x3b\x32\x90\x21\x22\ -\xd2\x5d\x37\xcc\xe6\xc9\x9f\x31\x16\x11\xac\x6e\xef\x9d\xbf\xd5\ -\xad\x77\x24\x03\xa7\x02\x20\xc2\x5f\xe0\xe7\xa3\x82\xfd\xba\x06\ -\x5c\x5e\x4e\x31\x44\xf8\xae\x3f\xaf\xd9\x95\x95\x5f\x6c\x87\xac\ -\x84\xf0\x7a\x8e\x0e\xee\xbd\xf5\x4a\xfc\xeb\x77\xef\xe5\xc9\x9f\ -\x31\x16\xf6\x1a\x2d\x02\x5f\x55\x86\xc7\xe4\x7f\x56\xab\x15\x57\ -\xbe\x73\x90\xfc\x5a\xb1\xed\xd7\x04\xc0\x9c\xa0\xdc\x07\x20\xd3\ -\x9f\xd7\xbc\x90\xcb\xed\xc1\x67\x9b\xf6\x04\x72\x88\xb0\x73\xe3\ -\x55\xd3\xf1\xe3\x27\xee\xe6\xc9\x9f\x31\x16\xd6\x64\xc5\x5b\xec\ -\xb7\xf7\x4c\x68\x6d\xf7\xf3\x85\x4b\x81\x24\x7b\xf0\x7b\x7f\x5e\ -\xd3\xaf\x09\x80\x20\x3c\xe7\xcf\xeb\x75\x65\xd3\x8e\x12\x98\x2d\ -\xb6\x40\x0f\x13\x36\x66\x4e\x1b\x87\x5f\xfd\xe0\x01\x9e\xfc\x19\ -\x63\x61\x8b\x00\x9c\x31\x09\x6c\xae\x90\x70\xba\x43\xf4\x79\x6b\ -\x79\xa8\x30\x39\xe8\x31\x7f\x5e\xcf\x6f\x09\x40\x49\x03\x4d\x00\ -\xc4\x1c\x7f\x5d\xaf\x3b\x9f\x6d\xe4\xbb\x7f\x5f\x8d\x1b\x59\x80\ -\xdf\xfd\xcb\xe3\x30\x1a\x42\xe0\xf4\x0b\xc6\x18\xeb\x23\xc2\xd7\ -\xcb\xfd\xa7\x24\x94\xd4\x0a\x38\x82\xfc\xa8\xdf\x81\xea\x74\x88\ -\xd4\xe5\x07\xc9\x6f\x07\x04\xf9\xaf\x08\x90\xd4\x1f\xfa\xed\x5a\ -\xdd\xa8\xaa\x69\xe4\xc6\x3f\x3e\x1a\x9c\x91\x86\x3f\xfe\xea\x69\ -\xc4\xc7\xc6\xe8\x1d\x0a\x63\x8c\xf9\x95\xc3\xf3\x75\x63\x9f\x4e\ -\x01\x93\x0f\xfd\x63\xc2\x05\x01\xe8\xb4\xd1\x6f\x01\xf8\xe5\x00\ -\x1c\xbf\x24\x00\x65\xcd\x94\xe8\xf2\xa8\x0f\xf9\xe3\x5a\x3d\xf9\ -\x7c\x33\xdf\xfd\xfb\x22\x3a\x2a\x0a\xbf\xff\xb7\xc7\x91\x96\x92\ -\xa4\x77\x28\x8c\x31\xe6\x17\x6e\x05\xa8\x35\x09\xd4\x9a\x04\x3a\ -\xec\x7d\xef\x20\x1b\x2e\xda\x1d\x62\xca\x8a\x12\xca\x5c\x50\x28\ -\x5a\x06\x7a\x2d\xbf\x24\x00\x6e\x59\x79\x00\x10\xc9\xfe\xb8\x56\ -\xf7\x63\xc8\x58\xf7\x55\x51\x20\x87\x08\x1b\x3f\x7b\x6e\x01\xc6\ -\x8d\x2c\xd0\x3b\x0c\xc6\x18\xf3\x8b\x8a\x36\x81\xe3\x2d\x02\x32\ -\x6f\xfe\x82\x5b\x81\x30\x7b\xd4\xff\x05\xf0\xd4\x40\xaf\xe5\x97\ -\x1a\x00\x22\x3c\xe9\x8f\xeb\xf4\x64\x4f\xc9\x71\x2e\xfe\xf3\xc1\ -\x7d\xb7\x5e\x85\x9b\xaf\x9e\xa1\x77\x18\x8c\x31\xe6\x17\xe5\xad\ -\x02\x47\x1a\x79\xf2\x3f\x57\xa7\x4b\xea\x57\xb3\xbd\x0b\x0d\x38\ -\x01\x28\xad\xa5\x31\x80\x98\xe9\x8f\x60\x7a\xb2\x69\x3b\xdf\xfd\ -\xf7\x66\xcc\x88\x21\xf8\xfe\xa3\x77\xea\x1d\x06\x63\x8c\xf9\x85\ -\xc5\x05\x1c\x6f\xe6\x1d\x4c\x17\x32\x3b\x91\xfc\xee\x7e\xba\x61\ -\xa0\xd7\x19\x70\x02\x40\x42\x7d\x0a\xfe\x3b\xe5\xb7\x4b\x76\x87\ -\x0b\x3b\x8b\x8f\x06\x72\x88\x90\x17\x17\x1b\x8d\x85\x3f\x79\x04\ -\x51\x46\xae\xf8\x67\xcc\x9f\xa6\x4f\x1a\x8d\x91\x05\xd9\x7a\x87\ -\x11\x91\x0e\x35\x08\x28\x91\xfa\xb0\xbf\x17\x36\x05\xbf\x19\xe8\ -\x35\x06\x54\x03\xb0\x95\xc8\x48\x0d\xea\x23\x81\xae\xc6\xd8\xbe\ -\xef\x30\x5c\xee\x10\x6f\xe6\x1c\x60\x3f\x7a\xfc\x6e\x14\xe4\x66\ -\xe9\x1d\x06\x63\x61\x23\x2b\x23\x15\x3f\x7c\x6c\x1e\xae\xbd\x7c\ -\x8a\xde\xa1\x68\x4a\x55\x09\x36\x87\x03\x09\x71\x71\x90\x24\xfd\ -\xee\xbe\x6b\x4d\x02\x2d\x36\xbe\xfb\xef\x4e\x87\x9d\x2e\x7f\xa3\ -\x88\xa2\x9e\x9d\x21\xfa\x3d\x39\x0e\x28\x01\x48\xae\x53\x6e\x86\ -\x10\x39\x03\xb9\x86\x2f\x36\xef\x3c\x10\xe8\x21\x42\xda\x55\x33\ -\x27\xe1\xce\xeb\x67\xe9\x1d\x06\x63\x61\x21\xda\x68\xc4\xfd\x77\ -\xce\xc5\xa3\xf3\x6f\x40\x5c\xec\x80\x8e\x35\xd1\x9d\x5b\x96\xd1\ -\xd0\xd8\x86\xa6\xb6\x4e\x74\x74\x5a\xd0\xd6\x69\x46\x6b\x87\x19\ -\x1d\x26\x0b\x5a\xdb\x4d\xb0\x3b\xdd\xb0\x5a\x1d\xf0\xc8\x32\x1c\ -\x4e\x37\x1c\x4e\x57\xb7\x6d\xd6\xa3\x8c\x06\xc4\xc6\xc4\x20\x3e\ -\x2e\x06\xa9\x29\x89\x48\x4d\x4e\x40\x4a\x52\x02\x52\x93\x12\x90\ -\x92\x9c\x80\xec\x8c\x74\xe4\xe7\x66\x62\x48\x4e\x26\x92\x13\xe3\ -\x07\x14\xb7\xdd\x0d\x1c\x0c\xd1\xe3\x7b\xb5\xe2\x90\x85\x21\xd1\ -\x80\x1f\x03\xf8\x53\x7f\xaf\x31\xa0\x04\x40\x12\xe2\x91\x40\xaf\ -\xce\xd8\x1d\x2e\x3e\xf6\xb7\x07\x89\xf1\x71\xf8\x97\x67\xfc\x52\ -\x0f\xc2\x58\xc4\x9b\x39\x6d\x1c\x7e\xfc\x44\xe8\xad\xa6\xb5\x77\ -\x98\x51\x7e\xba\x1e\xa7\xeb\x9a\x50\xdb\xd0\x82\xda\xc6\x16\xd4\ -\x36\xb4\xa2\xa9\xb5\x13\xaa\xea\x9f\x9e\xb8\x1e\x59\x81\x47\xb6\ -\xc3\x62\xb3\xa3\xa9\xb5\xa3\xc7\xd7\x26\x27\x25\xa0\x20\x27\x13\ -\xf9\xb9\x99\x18\x9e\x9f\x8d\x89\x63\x86\x62\xdc\xc8\x02\xc4\x44\ -\x47\xf5\x3a\x8e\x53\x06\xf6\xd4\x48\x61\xd7\xca\x37\x10\x3a\x1c\ -\x78\x0e\x7a\x24\x00\x3b\x5a\x28\x89\xdc\xea\xed\xfd\xfd\x7a\x5f\ -\xed\x29\x3d\x0e\xb7\x1c\xe6\xed\x9d\x06\xe0\xb9\x87\x6f\xc7\xa0\ -\xd4\x80\xee\xc0\x64\x2c\xec\xe5\x64\xa5\xe3\x47\x8f\xdf\x8d\xab\ -\x2e\xbb\x44\xef\x50\x7a\xd5\xdc\xda\x89\x23\xe5\xd5\xa8\x38\x5d\ -\x8b\x93\x55\x75\x28\x3f\x55\x87\xb6\x4e\xb3\xde\x61\x9d\xc7\x6c\ -\xb1\xe1\x88\xc5\x86\x23\x27\x4f\x7f\xf3\x31\xa3\xc1\x80\x31\xc3\ -\x87\xe0\x92\xb1\x43\x31\x71\xcc\x70\x4c\x1e\x3f\x1c\x99\xe9\x29\ -\xe7\x7d\x9d\x43\x06\x76\x9e\x96\x60\x75\x69\x1c\x70\x88\xea\x70\ -\x60\xc4\x40\x7a\x02\xf4\x3b\x01\x48\x70\x29\xf3\x49\x88\x81\xad\ -\xf3\xf8\x60\xfb\xbe\x43\x81\x1e\x22\x64\x4d\x18\x3d\x14\x77\x5e\ -\x7f\xb9\xde\x61\x30\x16\xb2\xa2\x8c\x06\xcc\xbb\x71\x0e\x9e\x79\ -\xf0\xd6\xa0\xec\x9a\xa9\xaa\x2a\xaa\xeb\x9a\x71\xf8\x78\x15\x0e\ -\x1e\x3f\x85\xc3\xc7\xab\x50\xdf\xd4\xa6\x77\x58\xfd\x22\x2b\x0a\ -\x8e\x56\x54\xe3\x68\x45\x35\xf0\xcf\x6d\x00\x80\x61\xf9\xd9\x98\ -\x33\x7d\x22\xe6\x4c\x9f\x80\x21\x43\x87\x63\x7f\x8d\x21\xec\xdb\ -\xf9\xfa\x93\x47\x81\xb0\xc8\xea\x7f\x02\xe8\x57\x27\xde\x7e\x27\ -\x00\x24\xa4\x07\x02\xdd\x8b\x49\x56\x14\xec\x2e\x3e\x16\xd0\x31\ -\x42\x95\xd1\x60\xc0\x2f\xbe\x77\xbf\xae\x45\x3a\x8c\x85\xb2\xd9\ -\xd3\x27\xe0\xc7\x4f\xde\x8d\xbc\xc1\x19\x7a\x87\x72\x9e\x86\xe6\ -\x76\xec\x2b\x3d\x8e\x7d\x87\x4e\xa0\xe8\x50\x39\x6c\x76\x87\xde\ -\x21\x05\xcc\xe9\x9a\x46\x9c\xae\x69\xc4\x7b\xab\x37\x23\x39\x25\ -\x0d\xa3\xc6\x4f\xc2\xa8\xb1\x93\x90\x3f\x7c\x34\x1f\x60\xe6\x23\ -\xb3\x4b\x2c\x80\x96\x09\xc0\xc1\x46\xca\x52\x14\xf5\xba\xfe\x7c\ -\x6d\x5f\x1c\x3e\x5e\x05\x6b\x18\xff\xf2\x0f\xc4\x5d\x37\x5e\x8e\ -\x11\x05\x01\xaf\xbf\x64\x2c\xec\xe4\x0d\xce\xc0\x8f\x9f\xbc\x1b\ -\xb3\xa7\x4f\xd0\x3b\x14\x00\xde\x1b\x9d\x03\x47\x2a\xb1\xb3\xf8\ -\x08\xf6\x95\x9e\xc0\x99\xfa\x66\xbd\x43\xd2\x85\xd9\xd4\x81\x92\ -\x3d\xdb\x50\xb2\x67\x1b\x92\x53\xd3\x31\x65\xfa\x6c\x4c\x2a\x9c\ -\x85\xf8\x44\x6e\x69\xde\x93\x4e\xa7\xc8\x7c\xbb\x94\xc6\x3c\x36\ -\x55\xf4\xb9\x58\xae\x5f\x09\x80\xa2\xa8\xf7\xf7\xf7\x6b\xfb\x62\ -\xdf\xc1\x13\x81\x1e\x22\x24\xc5\xc5\xc5\xe0\xf1\x7b\x6f\xd4\x3b\ -\x0c\xc6\x42\x4a\x4c\x74\x14\x1e\x9a\x77\x2d\x1e\xbe\xfb\x3a\x44\ -\x47\xf5\x5e\x8c\x16\x48\x2e\xb7\x07\x45\x87\x4e\x62\x67\x71\x19\ -\xb6\xef\x3b\x82\x0e\x93\x45\xd7\x78\x82\x8d\xb9\xb3\x1d\xdb\x37\ -\xaf\xc1\xae\x2f\xbf\xc0\xa8\x71\x93\x30\x79\xfa\x6c\x0c\x1d\x31\ -\x06\xe0\x55\x81\x8b\x10\x01\x1e\x0f\xfe\x03\xc0\x23\x7d\xfd\xda\ -\x7e\x4e\xe2\x74\x7f\x80\x7b\xff\x00\xe0\x04\xa0\x3b\x8f\xdf\x73\ -\x03\x1f\xf4\xc3\x58\x1f\x5c\x35\x73\x12\x7e\xf4\xd8\x3c\xe4\x64\ -\xa5\xeb\x16\x83\xac\x28\xd8\x57\x7a\x02\x1b\xb6\x15\x61\xfb\xfe\ -\x23\xdc\xdb\xc4\x07\x8a\xa2\xe0\x44\x59\x29\x4e\x94\x95\x22\x3d\ -\x73\x30\x2e\x9d\x7d\x2d\x26\x4e\xb9\x14\x12\x1f\x71\x7e\x1e\xab\ -\x93\x6e\xed\xcf\xd7\xf5\x39\x01\x38\xdc\x44\x83\x3d\xb2\x1a\xf0\ -\xca\x33\x93\xd9\x86\xf2\xaa\xda\x40\x0f\x13\x72\x06\x67\xa4\xe1\ -\xde\x5b\xaf\xd2\x3b\x0c\xc6\x42\x42\x41\x6e\x16\x7e\xf2\xe4\x7c\ -\x5c\x36\x75\xac\x6e\x31\x1c\xad\xa8\xc6\xfa\xaf\x8a\xb0\x79\x67\ -\x29\x3a\xcd\x56\xdd\xe2\x08\x75\xed\x2d\x4d\x58\xff\xe9\xfb\xd8\ -\xfd\xd5\x7a\xcc\xbc\xe2\x7a\x5c\x52\x38\x0b\x06\x4e\x04\x00\x00\ -\x9d\x0e\x91\xfe\x41\x29\x8d\xf9\x4e\x1f\x1f\x03\xf4\x39\x01\xf0\ -\x78\xd4\xf9\x10\xfe\x39\x44\xa8\x27\xfb\x0f\x9f\x84\xaa\x72\x0f\ -\xc8\x0b\x3d\x7e\xef\x8d\x3e\xed\xa5\x65\x2c\x92\xc5\xc5\x46\xe3\ -\xb1\x7b\x6e\xc4\xfd\x77\xcc\xd5\xa5\x3d\xb6\xcd\xee\xc0\xfa\xed\ -\x25\xf8\x6c\xc3\x6e\x54\x54\xd7\x69\x3e\x7e\x38\x33\x77\xb6\x63\ -\xe3\x9a\x15\xd8\xbf\x6b\x0b\xae\xbc\xfe\x0e\x8c\x9d\x30\x25\xe2\ -\x1f\x0d\xa8\x00\x6c\x6e\xf5\x17\x40\xdf\x0e\xe6\xeb\xfb\x23\x00\ -\x21\xee\xd1\xe2\x24\xe6\x92\x23\xe5\x01\x1f\x23\xd4\x64\x0d\x4a\ -\xc5\xcd\x73\x2f\xd5\x3b\x0c\xc6\x82\xda\xb5\x97\x4f\xc1\x0f\x1f\ -\x9f\x87\xac\x41\xa9\x9a\x8f\x7d\xea\x4c\x3d\x3e\x5c\xb3\x0d\x5b\ -\x76\x1d\x80\xc3\xe9\xd6\x7c\xfc\x48\xd2\xd9\xde\x8a\xcf\x57\x2c\ -\x45\xf1\x90\x61\xb8\xf6\xb6\x7b\x90\x9d\x1b\xd9\x47\xa0\x5b\x5c\ -\xa2\xcf\x7d\x79\xfa\x94\x00\xec\xad\xa5\x41\x80\x7a\x75\x5f\x07\ -\xe9\x8f\xd2\xa3\xa7\xb4\x18\x26\xa4\x3c\x70\xd7\x35\x7c\xd8\x0f\ -\x63\xdd\x28\xc8\xcd\xc2\x8f\x9f\xb8\x1b\x33\xa7\x8d\xd3\x7c\xec\ -\x43\xc7\x4e\x61\xd9\xea\xcd\xd8\x5d\x72\x0c\x44\xbc\x72\xa9\xa5\ -\xfa\xda\xd3\x78\xef\x1f\x2f\x62\xfc\xe4\x19\xb8\xf6\xe6\xbb\x11\ -\x1b\x9f\xa0\x77\x48\xba\x30\x39\x45\xe6\x7b\x25\x34\xf4\xa1\x42\ -\x51\xed\xeb\xd7\xf4\x29\x01\x30\x0a\xe5\x4e\x40\x04\xbc\xfa\xbf\ -\xd3\x6c\x45\x4d\x84\x6e\x85\xe9\x4e\x5a\x4a\x12\xee\xb8\x8e\xfb\ -\xfd\x33\x76\xa1\xb8\xb8\x18\x3c\x79\xdf\x4d\xb8\xef\xb6\xab\x60\ -\xd4\xf0\x99\xb0\xaa\x12\xbe\xdc\x7b\x08\x6f\x7d\xb4\x01\xa7\xce\ -\xd4\x6b\x36\x2e\xbb\x18\x11\xe1\xe8\xc1\xfd\xa8\x3e\x75\x02\x37\ -\xde\x71\x3f\x46\x8e\x0d\xfe\x8e\x8e\xfe\xa6\x10\xe0\x54\xf1\x0b\ -\x00\xdf\xf3\xf5\x6b\xfa\x34\x99\x0b\x48\x77\x69\xb1\xfc\x7f\xe8\ -\x58\x15\x67\xd1\x17\xb8\xef\xd6\xab\x10\x1b\x13\xda\x07\x93\x30\ -\xe6\x4f\x42\x08\x5c\x7f\xc5\x34\xfc\xe0\xd1\x3b\x91\x91\x96\xd2\ -\xfb\x17\xf8\x09\x11\x61\xcb\xae\x83\x78\xeb\xe3\xf5\xa8\xaa\x69\ -\xd4\x6c\x5c\xd6\x3b\x9b\xc5\x8c\x4f\x96\x2f\xc2\x25\xd3\x66\xe2\ -\xda\x5b\xe6\x23\x3a\x26\x56\xef\x90\x34\x65\x71\xd2\x9d\x08\x44\ -\x02\x50\x5e\x4e\x31\x16\x04\xbe\xf9\x0f\x00\x1c\x3c\x56\xa9\xc5\ -\x30\x21\x23\x3a\x2a\x0a\x77\xdd\xc0\x2d\x7f\x19\x3b\x6b\x44\x41\ -\x2e\x5e\x78\xea\x6e\x4c\x9b\x38\x4a\xd3\x71\x0f\x1e\xab\xc4\xcb\ -\x6f\x7f\x86\x63\x15\x67\x34\x1d\x97\xf5\xcd\x91\x03\x7b\x51\x53\ -\x55\x8e\x3b\x16\x3c\x81\xec\xbc\xc8\xa9\x0d\x30\x3b\x45\xce\xb2\ -\x3d\x94\xfc\xf0\x2c\xe1\xd3\xe1\x10\x3e\x27\x00\xd6\x58\xe5\x3a\ -\x40\x24\xf6\x3f\x34\xdf\x95\x9d\xf4\xf9\x11\x46\x44\xb8\x66\xf6\ -\x14\xa4\x24\x47\xe6\x73\x2d\xc6\xce\x95\x10\x1f\x87\xa7\xee\xbf\ -\x19\xf7\xdc\x7c\x05\x0c\x86\x80\x6f\x46\xfa\x46\x63\x4b\x3b\x5e\ -\x7e\xfb\x53\x7c\xb9\x87\xcf\x26\x09\x15\xa6\xce\x76\xbc\xbf\xe4\ -\x2f\xb8\xfa\xc6\x79\x28\x9c\x15\x19\x5b\xa7\x65\x15\xc2\x23\xd4\ -\xef\x01\xf8\x7f\xbe\xbc\xde\xe7\x04\x40\x95\xc4\x6d\x5a\x6c\xb4\ -\x50\x14\x15\xe5\xa7\x79\xdb\xcc\xb9\xee\xbe\x69\x8e\xde\x21\x30\ -\xa6\x2b\x21\x04\x6e\xbe\x7a\x06\xbe\xf7\xf0\x1d\x48\x4f\xd5\xae\ -\x09\x96\xdb\xe3\xc1\x7b\xab\xb7\x60\xd9\x27\x9b\xb9\x71\x4f\x08\ -\x52\x14\x05\x5b\xbe\x58\x89\x86\xda\xd3\xb8\xe9\xae\x07\x60\xd4\ -\xb9\x03\xa4\x16\x9c\x24\x2d\x80\xbf\x13\x00\x01\xf4\xab\xd3\x50\ -\x5f\x9d\xaa\x69\xe0\x3f\xb4\x73\x8c\x1e\x96\x87\x4b\xc6\x0c\xd3\ -\x3b\x0c\xc6\x74\x33\x7a\x58\x1e\x5e\x78\x6a\x3e\x26\x8f\x1f\xa1\ -\xe9\xb8\x3b\x8b\xca\xf0\xd7\xa5\x9f\x84\xec\xe9\x7b\xec\x5b\xc7\ -\x0e\x17\xa3\xb3\xa3\x15\xf3\xbe\xf3\x34\x12\x92\xc2\xfb\xf8\x74\ -\xb3\x93\x26\xf9\xfa\x5a\x9f\x12\x80\xe2\x1a\x9a\x0c\xa8\xc3\xfa\ -\x1d\x51\x1f\x9c\xa8\xe4\xee\x7f\xe7\xe2\xbb\x7f\x16\xa9\x92\x12\ -\xe2\xf1\xcc\x03\xb7\x60\xde\x8d\xb3\x21\x49\xda\x2d\xf7\xd7\x34\ -\xb4\xe0\xaf\x4b\x3f\xc1\x9e\x12\x3e\x89\x34\x9c\x34\xd4\x56\xe3\ -\xbd\x45\x2f\x62\xfe\xc3\xcf\x22\x23\x2b\x7c\x0f\x52\xb3\xb9\x45\ -\xd4\x7b\x25\x74\xfd\x43\x85\x62\x53\x6f\xaf\xf5\x6d\x05\x40\x52\ -\x6f\x1e\x70\x54\x3e\x3a\x5e\xc9\xc5\x35\x67\x45\x19\x0d\x98\x7b\ -\xf9\x14\xbd\xc3\x60\x4c\x53\x42\x08\xdc\x78\xd5\x74\xfc\xf0\xd1\ -\x3b\x35\x3d\xf3\xc2\xe9\x72\x63\xf9\xa7\x5b\xb0\xec\x93\x2d\x70\ -\x7b\x78\x15\x32\x1c\x99\x4d\x1d\x78\x7f\xc9\xdf\x30\xff\xe1\xef\ -\x22\x2f\x7f\xb8\xde\xe1\x04\x8c\xd3\x43\xcf\x02\xf0\x4f\x02\x20\ -\x20\x6e\xd0\x62\xfb\x1f\x00\x9c\xac\xe2\xe7\xff\x67\xcd\x9c\x36\ -\x0e\xc9\x89\xf1\x7a\x87\xc1\x98\x66\xc6\x8d\x2c\xc0\x4f\x9f\x99\ -\x8f\x09\xa3\x86\x6a\x3a\xee\x96\x5d\xa5\x78\xf9\x9d\x4f\xd1\xdc\ -\xda\xa9\xe9\xb8\x4c\x7b\x2e\xa7\x1d\x2b\xdf\x79\x0d\x77\x7d\xe7\ -\x29\x0c\x1d\xa9\xdf\x19\x11\x81\x64\xf6\xc0\xa7\xaa\xc7\x5e\x13\ -\x80\xad\x55\x14\x0b\xa8\x9a\xac\x43\x13\x11\xaa\x6a\x79\x5f\xed\ -\x59\xd7\xcd\x29\xd4\x3b\x04\xc6\x34\x91\x9c\x94\x80\xe7\x1e\xbc\ -\x0d\xb7\x5f\x37\x0b\x92\xa4\x5d\x5f\xf7\xaa\x9a\x46\xbc\xb4\x64\ -\x15\xb7\x1e\x8f\x30\x6e\xb7\x0b\xab\xde\xfb\x07\xee\x7e\xf0\x19\ -\x0c\x1b\xa5\x7d\xe7\xc8\x40\x33\x3b\x91\xb5\xa2\x88\x52\x16\xcc\ -\x10\xa6\x9e\x5e\xd7\x6b\x02\x90\x1a\x2d\x5f\x49\x90\xe2\xfc\x17\ -\x5a\xf7\x1a\x5b\x3a\xe0\x70\xb8\xb4\x18\x2a\xe8\xc5\xc5\x46\xe3\ -\x8a\x19\x91\xd7\xcd\x8a\x45\x16\x49\x12\xb8\xe1\xca\xe9\xf8\xd1\ -\x63\x77\x21\x35\x59\x93\x5d\xc6\x00\x00\x87\xd3\x8d\xf7\x3f\xdb\ -\x82\x77\x57\x6d\x82\x47\x56\x34\x1b\x97\x05\x0f\x45\x91\xb1\xfa\ -\x83\xc5\x98\xff\xd0\x77\x51\x30\x7c\x8c\xde\xe1\xf8\x95\xa2\x0a\ -\xd8\x55\x3c\x03\xe0\x4f\x3d\xbd\xae\xd7\x04\x40\x85\x74\x83\x56\ -\xf9\x38\xdf\xfd\x7f\x6b\xce\xf4\x89\x88\x8b\xe5\xce\x7f\x2c\x7c\ -\x4d\x1c\x3d\x0c\x3f\x7d\x7a\x3e\xc6\x8e\xcc\xd7\x6c\x4c\x22\xc2\ -\xfa\x6d\xc5\x78\x75\xd9\xe7\x68\xef\xf0\xa9\x57\x0a\x0b\x63\xb2\ -\xc7\x83\x4f\x96\x2f\xc2\xbd\x8f\x7c\x0f\x79\x05\xda\xee\x32\x09\ -\x34\x87\x4c\xf3\x31\xd0\x04\x40\x00\x37\xf8\x2d\xa2\x5e\x54\x9d\ -\x69\xd0\x6a\xa8\xa0\x37\x87\xef\xfe\x59\x98\x4a\x4e\x8c\xc7\xe3\ -\xf7\xdd\x84\x7b\x6f\xb9\x52\xd3\xe5\xfe\xf2\xd3\x75\x78\x69\xf1\ -\x2a\x1c\x3a\xce\x07\x8d\xb1\x6f\x79\xdc\x6e\x7c\xb2\x7c\x11\x1e\ -\x7c\xfa\x27\x48\xcf\x18\xac\x77\x38\x7e\x63\x97\x45\xaf\xdb\x01\ -\x7b\x4c\x00\xca\x6a\x28\xdd\x05\x75\xb2\xff\x42\xea\x19\xf7\xd5\ -\xf6\x92\x24\x09\x97\x4d\x09\xcf\xe2\x14\x16\xb9\x24\x49\xc2\xdd\ -\x37\xcd\xc6\x33\x0f\xdc\x8a\xc4\x78\x4d\x9e\x2a\x02\x00\xcc\x56\ -\x3b\x16\xbd\xbf\x16\x9f\x6e\xdc\x0d\x55\x55\x35\x1b\x97\x85\x0e\ -\xa7\xc3\x8e\x95\xcb\xde\xc0\x83\x4f\xfd\x24\x6c\xfa\x04\x58\x9c\ -\x48\x5c\x51\x4a\x79\x0b\xa6\x8a\x6e\x2b\xeb\x7b\x4c\x00\xdc\x06\ -\xe5\x2a\x90\xd0\x6c\x03\x6e\x5d\x23\x37\xdc\x00\x80\xf1\x23\x0b\ -\xb8\xf5\x2f\x0b\x2b\x93\xc7\x8f\xc0\x4f\x9f\x9e\x8f\x51\x43\xf3\ -\x34\x1b\x53\x55\x09\x6b\x36\xef\xc1\xeb\xcb\xff\x09\xb3\xc5\xa6\ -\xd9\xb8\x2c\x34\x99\x3a\xda\xf0\xc9\xf2\x45\xb8\xff\xc9\x1f\x21\ -\x2a\x2a\xf4\x1f\xbf\xaa\x00\xec\xb2\xfa\x04\x80\xff\xee\xee\x35\ -\xbd\x3c\x02\x10\x9a\x36\x50\xae\x6b\x6a\xd5\x72\xb8\xa0\x75\xf9\ -\xf4\xf1\x7a\x87\xc0\x98\x5f\xa4\xa7\x25\xe3\xfb\x0f\xdf\x81\x9b\ -\xae\x9a\x0e\x21\xb4\x5b\xee\x3f\x51\x59\x83\x17\x17\xaf\x42\x59\ -\xf9\x69\xcd\xc6\x64\xa1\xaf\xb1\xfe\x0c\xd6\x7f\xf6\x01\x6e\xbf\ -\xe7\x51\xbd\x43\xf1\x0b\x97\x2c\xdd\x8a\xfe\x26\x00\x44\xb8\xda\ -\xef\x11\x75\xc3\xee\x74\xa1\xc3\x64\xd5\x6a\xb8\xa0\x36\x6b\x5a\ -\xf8\x6d\x4b\x61\x91\xc5\x60\x90\x30\xff\xa6\x2b\xf0\xf4\x77\x6e\ -\x46\x82\x96\xcb\xfd\x16\x1b\x96\x7e\xb4\x01\x2b\xd7\x6d\x87\xaa\ -\xf2\x91\xe2\xac\xef\x8e\x1f\x2a\x46\x4e\x6e\x01\xa6\x5f\x3e\x57\ -\xef\x50\x06\xcc\xea\x41\x8f\xc5\x64\xdd\x26\x00\x7b\xda\x28\x19\ -\x4e\x55\xb3\x36\x74\x0d\x4d\x6d\x20\xe2\x3f\xd8\xc4\xf8\x38\x8c\ -\x19\xae\x5d\x55\x34\x63\xfe\x36\x6d\xe2\x48\xbc\xf0\xd4\x3d\x18\ -\x51\xa0\x5d\xbb\x55\x45\x51\xb1\x6a\xfd\x0e\x2c\xf9\x60\x1d\xac\ -\x76\x87\x66\xe3\xb2\xf0\xf4\xd5\x86\x4f\x91\x95\x9b\x8f\xfc\xa1\ -\x23\xf5\x0e\x65\x40\x2c\x4e\x24\xad\xaa\xa0\xac\xf9\xa3\x44\x73\ -\x57\x9f\xef\x36\x01\x88\xb1\x2b\x57\x90\x24\x0c\x81\x0b\xed\x7c\ -\xfc\xfc\xdf\x6b\xd2\xd8\x61\x9a\x56\x46\x33\xe6\x2f\x83\x52\x93\ -\xf1\xbd\x47\xb4\x5f\xee\x2f\x3d\x5a\x89\x97\x96\xac\x42\x65\x75\ -\xbd\x66\x63\xb2\xf0\xa6\xaa\x2a\xfe\xf9\xf1\xdb\x78\xfc\x7b\x3f\ -\x47\x6c\x7c\xe8\xd6\x63\xa9\x04\x98\x4d\xea\xe3\x00\xfe\xaf\xab\ -\xcf\x77\x9b\x00\xa8\x42\xcc\xd1\x72\x1a\x6a\x68\x69\xd7\x70\xb4\ -\xe0\x35\x69\x5c\xf8\xf6\xa7\x66\xe1\xc9\x68\x30\xe0\xee\x9b\xe6\ -\xe0\x99\xef\xdc\x82\xf8\xf8\x58\xcd\xc6\x6d\xed\x30\xe1\xb5\x65\ -\x6b\xb0\x61\x5b\x31\xaf\x1e\x32\xbf\xb3\x9a\x4d\x58\xff\xd9\x07\ -\xb8\xeb\x3b\x4f\xe9\x1d\xca\x80\x38\x3d\xe2\x36\xf4\x35\x01\x80\ -\x24\x2e\x87\x86\x7f\x54\xad\xed\x3d\x76\x2c\x8c\x18\x97\x70\x02\ -\xc0\x42\xc8\x8c\xc9\x63\xf0\xd3\xa7\xe6\xa3\x20\x4f\xbb\xfd\xd3\ -\x1e\x59\xc1\x47\x6b\xbe\xc2\x5b\x1f\x6f\x80\xdd\xc9\x9d\x43\x59\ -\xe0\x94\x1f\x3b\x84\xc3\xc5\xbb\x31\x69\xfa\xe5\x7a\x87\xd2\x6f\ -\x0e\x59\x4c\xec\xee\x73\x5d\x26\x00\x44\x24\x1d\xa8\x57\xa7\x07\ -\x2e\xa4\x8b\x35\xb7\x75\x68\x39\x5c\x50\x32\x18\x24\x8c\xd3\xb0\ -\x2b\x1a\x63\xfd\x95\x99\x9e\x82\x67\x1f\xba\x1d\x37\x5f\x3d\x43\ -\xd3\x71\x4b\x8e\x94\xe3\xa5\x25\xab\xb8\x67\x08\xd3\xcc\x96\x75\ -\xab\x50\x30\x62\x0c\x52\xd2\x06\xe9\x1d\x4a\xbf\xd8\x5c\x48\x5f\ -\x41\x64\x58\x20\xc4\x45\x3d\xaf\xbb\x4c\x00\x4a\x6a\x71\x89\x90\ -\xa0\x69\x37\x84\x96\x36\x8b\x96\xc3\x05\xa5\x51\xc3\xf2\x10\x1f\ -\x1b\xa3\x77\x18\x8c\x75\x2b\xda\x68\xc4\x77\xee\x9a\x8b\x47\xe7\ -\xdf\x80\xd8\x18\xed\xf6\x4a\x37\x34\xb7\xe3\xef\x6f\xaf\xc6\xb6\ -\xbd\x87\x35\x1b\x93\x31\xc0\xdb\x29\x70\xd3\x3f\x3f\xc6\x3d\x0f\ -\x3f\xab\x77\x28\xfd\xe2\x56\x20\xdc\x07\x70\x23\x80\x2f\x2e\xfc\ -\x5c\x97\x09\x80\x64\x50\x66\x12\x69\x53\x01\xd0\xee\x00\xca\x5b\ -\x05\xaa\x1b\x79\x05\x60\xf4\xb0\x5c\xbd\x43\x60\xac\x5b\xd3\x27\ -\x8d\xc6\x4f\x9f\xba\x07\x43\x87\x68\xbb\xdc\xff\xc9\xfa\x9d\x58\ -\xfc\xfe\x5a\x5e\xee\x67\xba\xa9\x2a\x3f\x8a\x63\x87\x8a\x30\x7e\ -\xb2\xb6\x2b\x5e\xfe\xe2\x26\xcc\x83\xaf\x09\x00\x91\x98\x19\xe8\ -\x80\x14\x02\x0e\x35\x08\x54\x77\x08\x80\x08\x56\x0b\x1f\xcc\x31\ -\x22\x5f\xbb\x6d\x53\x8c\xf9\x2a\x77\xf0\x20\xfc\xe8\xf1\x79\xb8\ -\xf2\x52\x6d\xcf\xa7\xd8\xbe\xff\x08\xfe\xfe\xd6\x6a\xd4\x37\xf1\ -\x0e\x21\xa6\xbf\x2d\xeb\x56\x61\xf8\xe8\x09\x88\x8d\x8b\xd7\x3b\ -\x94\x3e\x73\xbb\xd1\x65\x11\x43\xd7\x45\x80\x02\x97\x21\x80\xf5\ -\x7f\xb2\x0a\xec\x3e\x23\xa1\xed\xeb\xee\x9c\x6e\x97\x13\x8a\x22\ -\x07\x6e\xc0\x10\x31\x8a\x57\x00\x58\x10\x89\x32\x1a\x30\xef\xc6\ -\x39\x78\xe6\xc1\x5b\x35\x7d\x34\x55\xdb\xd8\x8a\xbf\xbd\xf9\x09\ -\x76\x95\x1c\xd5\x6c\x4c\xc6\x7a\xe3\xb0\xd9\xb0\x73\xeb\x5a\x5c\ -\x77\xeb\xbd\x7a\x87\xd2\x67\x76\x0f\x75\x79\xd4\xe1\x45\x09\xc0\ -\xd6\x2a\x8a\x05\xd4\x80\xb6\xa2\x3b\x50\x27\xbe\x99\xfc\x01\xc0\ -\xe1\xb0\x07\x72\xb8\x90\x31\x3c\x3f\x5b\xef\x10\x18\x03\xe0\xbd\ -\xeb\x7f\xef\xaf\xbf\x44\xee\x60\xed\x0a\x9f\x1c\x4e\x37\xde\x5e\ -\xb9\x01\x1f\x7e\xfe\x25\x3c\xf2\x45\xf5\x4a\x8c\xe9\xee\xe0\xfe\ -\x9d\x98\x32\x7d\x36\x32\x06\x87\xd6\xcd\x9a\xc5\x2d\x12\x56\x94\ -\x51\xe2\x82\x89\xe2\xbc\x76\xbb\x17\x1d\xf4\x93\x66\xc4\x64\x10\ -\xa2\x02\x15\x48\x9d\x59\xa0\xce\x7c\x7e\x7d\x81\xc3\xc1\x07\x75\ -\xa4\xa5\x24\x21\x2d\x25\x49\xef\x30\x18\x03\x00\x0c\xce\x48\xd3\ -\x74\xf2\xdf\xb2\xab\x14\x0f\xfd\xf8\x0f\x58\xf6\xc9\x66\x9e\xfc\ -\x59\xd0\x52\x55\x15\x9b\xd7\xae\xd2\x3b\x8c\x3e\x53\x54\xc0\xe9\ -\xc2\xed\x17\x7e\xfc\xa2\x15\x00\x45\xa8\xd3\x02\x55\xfe\x27\xab\ -\xc0\x91\xc6\x8b\xaf\xee\xb4\xf3\x0a\x00\xdf\xfd\xb3\x48\x54\x5d\ -\xdb\x84\x17\x97\xac\x44\xf1\xe1\x72\xbd\x43\x61\xcc\x27\x35\xa7\ -\xcb\x51\x79\xb2\x0c\x23\xc7\x74\xbb\xbd\x3e\x28\xb9\x65\xdc\x0c\ -\xe0\x83\x73\x3f\x76\x51\x02\x20\x80\x69\x81\x0a\xe0\x54\x9b\x80\ -\xc3\x73\xf1\xc7\x1d\x76\x3e\x04\x28\x2f\x3b\x34\xf7\x98\x32\xd6\ -\x1f\x0e\xa7\x1b\xef\x7f\xb6\x05\xcb\x56\x6d\x86\x5b\xe6\xfa\x1f\ -\x16\x5a\xb6\x6f\x5a\x83\x11\xa3\x27\x68\xda\xf2\x7a\xa0\x5c\x32\ -\x2e\xea\xed\x73\x71\x11\xa0\x44\xd3\x10\x80\x2d\x80\x8a\x0a\x54\ -\xb6\x77\x7d\x5d\xa7\x83\x0f\xef\xc8\xce\x4c\xd3\x3b\x04\xc6\x34\ -\xb1\x71\x47\x09\x5e\x79\xfb\x33\xb4\x76\x70\xf7\x4f\x16\x9a\x5a\ -\x9b\xea\x71\xa2\xec\x00\xc6\x5d\x52\xa8\x77\x28\x3e\x73\x29\x54\ -\x70\xe1\xc7\xce\x4b\x00\x56\x10\x19\x50\xaf\x4e\x0a\xc4\xe0\xa7\ -\x3b\x04\x5c\xdd\x24\xfa\x6e\xb7\x33\x10\x43\x86\x94\xec\x8c\x74\ -\xbd\x43\x60\x2c\xa0\x4e\x9d\x69\xc0\x4b\x4b\x56\xe2\x40\x59\xa5\ -\xde\xa1\x30\x36\x60\x3b\xb7\xac\xc5\x98\x09\x53\x21\x49\x17\x95\ -\xd2\x05\x25\x87\x5b\x24\x11\x91\x24\x84\x50\xcf\x7e\xec\xbc\x04\ -\x60\x4c\x1d\x46\xaa\x02\x7e\x3f\xbc\x5b\x21\xa0\xa2\xb5\xfb\x55\ -\x05\x8f\xc7\xed\xef\x21\x43\x4e\x76\x16\xaf\x00\xb0\xf0\x74\x76\ -\xb9\xff\xdd\x55\x9b\xb8\xc0\x8f\x85\x8d\x8e\xb6\x16\x9c\x38\x52\ -\x12\x32\xcd\x81\x5c\x0a\xc4\x07\x87\x70\x29\x80\xbd\x67\x3f\x76\ -\x5e\x02\xa0\x48\xca\x44\x11\x80\xe5\xff\x33\x1d\x02\x8e\x1e\x1e\ -\xf3\x79\x5c\xdc\xe1\x2b\x3b\x93\x57\x00\x58\xf8\xd9\x59\x54\x86\ -\x3f\x2f\xfe\x18\xcd\xad\x9d\x7a\x87\xc2\x98\xdf\xed\xdd\xb1\x19\ -\xe3\x27\x4d\x07\x42\xa4\x16\xc0\xa3\xa8\x37\xa0\xbb\x04\x00\xaa\ -\x98\x00\x3f\xff\x77\x38\x65\xe0\x58\x73\xcf\x17\x75\xbb\x23\x3b\ -\x01\x30\x18\x24\x64\xa4\xa5\xe8\x1d\x06\x63\x7e\x53\x59\x5d\x8f\ -\x17\x17\xaf\xc2\xc1\x63\xbc\xdc\xcf\xc2\x57\x6b\x53\x3d\x2a\xcb\ -\x8f\x86\xcc\x8e\x00\x97\x2c\x5d\x76\xee\xbf\x9f\x97\x00\x08\x81\ -\x09\xfe\x1e\xf0\x70\x83\x80\xbb\x97\x55\x3f\x8f\x3b\xb2\x1f\x01\ -\xa4\x24\x25\xc0\x60\x08\x8d\xe7\x48\x8c\xf5\xc4\x6a\x77\x60\xc9\ -\x07\xeb\xb0\x6a\xfd\x0e\x28\x8a\xda\xfb\x17\x30\x16\xe2\xf6\xef\ -\xd8\x14\x32\x09\x80\x47\xa6\xf3\x9a\xfc\x5d\xb8\x0b\xc0\xaf\x09\ -\xc0\xb1\xe6\x8b\x9b\xfe\x74\x19\x54\x84\xd7\x00\x24\x25\x86\x5e\ -\x6f\x69\xc6\xce\x45\x44\xf8\xe2\xcb\xfd\x78\xfd\xbd\x35\x68\xef\ -\xe4\x93\x3d\x59\xe4\xa8\xad\x3e\x85\xc6\xfa\x33\xc8\xce\xbd\xa8\ -\xc8\x3e\xe8\x38\x3c\xe2\xbc\x03\x67\xbe\xb9\xed\x24\x22\x09\xc0\ -\x58\x7f\x0c\xe2\x56\x80\x92\x3a\x81\x13\x2d\xbe\x3d\x4f\x50\xd5\ -\xc8\x2e\x0c\x4a\x4d\x4a\xd4\x3b\x04\xc6\xfa\xad\xfc\x74\x1d\x7e\ -\xf0\x1f\x2f\xe3\x7f\x5e\x79\x9f\x27\x7f\x16\x91\x0e\xec\xdb\xa1\ -\x77\x08\x3e\xb1\xcb\x48\x58\xe8\x9d\xeb\x01\x9c\xb3\x02\x70\xa8\ -\x09\x43\x81\xfe\xef\x00\x90\x55\xa0\xd3\x21\xd0\x68\x01\xce\x74\ -\xf6\xbe\xec\x7f\x2e\x55\x8d\xec\xa5\xc2\xa4\xc4\x58\xbd\x43\x60\ -\x61\x28\xca\x68\xc0\x82\xdb\xe7\x62\xfc\xa8\xc0\xdc\x99\x58\x6c\ -\x76\x2c\x5a\xbe\x16\xab\x37\xee\x8e\xf8\xbf\x61\x16\xd9\x4e\x1c\ -\x2e\xc1\xdc\x1b\xee\x42\x5c\x42\x82\xde\xa1\xf4\xc8\xa3\x40\x8c\ -\x2c\xc2\x0c\x00\xfb\x80\x73\x12\x00\x55\x91\x47\x75\x71\x34\xc0\ -\xf9\x5f\xac\x02\x36\x17\x60\xf3\x08\xd8\xdd\x80\xdd\x0d\xd8\x3c\ -\x80\xcd\x25\x60\x97\x01\xea\xe7\x09\x82\xaa\x12\xd9\x2b\x00\xc9\ -\x89\xc1\xfd\x4b\xc3\x42\xcf\xf4\x49\xa3\xf1\xc2\x93\xf3\x31\x2c\ -\x00\x2d\xa6\x89\x08\xeb\xb7\x15\xe3\x95\x77\x3e\x43\x87\x2f\xeb\ -\x2f\xd6\x00\x00\x20\x00\x49\x44\x41\x54\x89\xef\xf8\x19\x93\x65\ -\x0f\x8e\x1c\xd8\x83\x4b\xaf\xb8\x4e\xef\x50\x7a\x27\xd4\xcb\x71\ -\x61\x02\x40\x24\x8d\xba\x70\x07\x80\x4a\x40\xbb\x5d\xa0\xc5\x06\ -\x34\x5b\x05\x3a\x9d\xfd\x9f\xe4\x7b\x12\xe9\x77\x0f\xc9\x5c\x03\ -\xc0\xfc\x24\x6f\x70\x06\x9e\x7f\x62\x1e\xe6\xcc\x08\x4c\x51\xd2\ -\xf1\xca\x33\x78\x71\xf1\x2a\x1c\x2d\xaf\x0e\xc8\xf5\x19\x0b\x55\ -\x07\x8b\x77\xe1\xd2\x39\xd7\x06\xfd\x96\x40\x19\x98\x7a\xf6\x9f\ -\xbf\x49\x00\x4a\x1b\xc5\x7d\x46\x01\x18\x0d\x80\x5b\x06\x3a\x9d\ -\x02\x26\x87\xb7\x89\x4f\xa0\x45\x7a\x0d\x40\x62\x82\xdf\x7b\x2f\ -\xb1\x08\x13\x17\x1b\x8d\x47\xee\xbe\x1e\xdf\xb9\x73\x2e\xa2\xa3\ -\xfc\x7f\x98\xa7\xd9\x62\xc3\x1b\xcb\xd7\xe2\xf3\xcd\xbb\xa1\xaa\ -\x1a\xbc\x29\x30\x16\x62\x3a\xdb\x5b\x51\x5b\x73\x0a\x43\x0a\x46\ -\xea\x1d\x4a\x8f\x64\x45\x8c\x39\xfb\xcf\xdf\x24\x00\x16\x27\xc6\ -\xb5\xd9\xf5\xc9\x5c\x22\x7d\x05\x20\xda\x78\xf1\x91\x0c\x8c\xf9\ -\xea\xda\xd9\x53\xf1\xc3\x47\xef\x42\x56\x46\xaa\xdf\xaf\xad\xaa\ -\x84\xcf\x37\xed\xc6\x1b\xef\xaf\x85\xd9\xc2\xc7\x76\x33\xd6\x93\ -\xb2\x03\xfb\x82\x3e\x01\x70\x2b\x62\xc8\xd9\x7f\xfe\x66\xe6\x71\ -\xc9\x82\x7b\xd1\xea\xc4\x68\x34\xe8\x1d\x02\x0b\x41\x05\xb9\x59\ -\xf8\xc9\x93\xf3\x71\xd9\x54\xbf\x6c\xde\xb9\xc8\x89\xca\x1a\xbc\ -\xb8\x78\x15\xca\xca\x4f\x07\xe4\xfa\x8c\x85\x9b\x13\x65\xa5\xb8\ -\xf6\xd6\x7b\x10\x15\x15\xad\x77\x28\xdd\x72\xcb\xf8\xe6\xe8\x59\ -\x23\x00\x2c\x24\x92\x9c\x9b\x28\x16\xfe\x6e\x03\xe8\xa3\x50\x3a\ -\x52\x31\x10\x38\x01\x60\x7d\x91\x18\x1f\x87\x27\xef\xbf\x19\xf7\ -\xdc\x7c\x45\x40\x1a\x48\x75\x9a\xad\x78\xfd\xbd\x35\x58\xbb\x75\ -\x1f\x2f\xf7\x33\xd6\x07\x6e\x97\x13\x15\xc7\x0f\x7b\xdb\x03\x07\ -\x29\xa7\x07\xdf\x14\x9d\x19\x01\x20\x7f\x1f\x26\x55\x07\xe0\x0c\ -\x00\x5f\x09\x9d\x12\x8f\x60\x61\xe4\x47\x00\xcc\x07\x42\x08\xdc\ -\x78\xd5\x74\xfc\xe0\x91\x3b\x91\x9e\x9a\xe4\xf7\xeb\xab\x2a\x61\ -\xc3\xf6\x62\xfc\xfd\xed\xd5\x30\x99\x79\xb9\x9f\xb1\xfe\x38\x71\ -\xe4\x40\x50\x27\x00\x6e\x05\x62\x65\x11\xe5\xdc\x33\x43\x34\x78\ -\x67\x1e\x23\x2e\xeb\xe5\x6b\x02\x4a\x48\x91\x9d\x00\x44\xf1\x0a\ -\x00\xeb\xc5\x84\x51\x43\xf1\x93\xa7\xef\xc6\x84\x51\x43\x03\x72\ -\xfd\xc3\x27\xaa\xf0\xd2\xe2\x55\x38\x59\x55\x1b\x90\xeb\x33\xff\ -\xca\x48\x4b\x41\x6e\xf6\x20\x0c\x1e\x94\x8a\x41\x69\xc9\x48\x4b\ -\x49\x44\x52\x42\x3c\x62\xa2\xa3\x10\x15\xfd\x6d\x11\xa8\xd3\xe9\ -\x82\xc5\xe6\x40\x87\xc9\x82\xf6\x4e\x0b\x1a\x9a\xdb\x71\xaa\xa6\ -\x81\x13\xbc\x00\x3a\x5d\x71\x1c\x1e\xb7\x0b\x51\xd1\x31\x7a\x87\ -\xd2\x25\x02\xe0\x10\x98\x05\xe0\x13\x23\x00\x78\x3c\xea\xd4\xde\ -\x7a\x00\x04\x52\xc4\xaf\x00\x18\x38\x01\x60\x5d\x4b\x4b\x49\xc2\ -\x73\x0f\xdd\x86\x5b\xe6\x5e\x06\x29\x00\x89\x72\x7b\xa7\x05\xaf\ -\xbf\xb7\x06\x5f\x7c\xb9\x1f\x14\x88\x3d\xbe\x6c\x40\x24\x49\xc2\ -\xb0\x21\xd9\x98\x30\x3a\x1f\x13\x46\x0d\xc5\xc8\x61\x39\x18\x96\ -\x37\x18\x09\xf1\x03\xdb\x39\x64\x32\xdb\x50\x56\x5e\x8d\x92\x23\ -\xe5\x28\x3d\x7a\x0a\x27\xab\x6a\xf8\x71\x8f\x9f\xc8\xb2\x07\x95\ -\x27\xcb\x30\xee\x92\x42\xbd\x43\xe9\x96\x4c\x98\x8a\xb3\x09\x80\ -\x42\x62\x84\x9e\xc1\x44\xfa\x0a\x80\x24\xf1\x41\x40\xec\x7c\x06\ -\x83\x84\x7b\x6e\xbe\x02\x4f\xde\x7f\x33\x12\x07\xf8\x66\xdf\x15\ -\x45\x51\xb1\x6a\xfd\x0e\x2c\xf9\x60\x1d\xac\x76\x87\xdf\xaf\xcf\ -\xfa\x2f\x6b\x50\x2a\x66\x4e\x1b\x87\x99\x53\xc7\xe3\xd2\xc9\xa3\ -\x07\x3c\xd9\x77\x25\x25\x39\x01\xb3\xa7\x4f\xc0\xec\xe9\xde\xe3\ -\x5f\x5a\xda\x4d\xd8\xba\xab\x14\x9b\x76\x1e\xe0\x1e\x0f\x7e\x70\ -\xb2\xac\x34\xb8\x13\x00\x55\x1d\x03\x7c\x5d\x03\xa0\xa8\x22\x4f\ -\xcf\x60\x22\x7d\x05\x40\x96\x65\xbd\x43\x60\x41\xa4\x70\xe2\x28\ -\xfc\xe4\xa9\xf9\x18\x51\x90\xd3\xfb\x8b\xfb\xe1\xe0\xb1\x4a\xbc\ -\xb4\xf8\x13\x54\x54\xd7\x05\xe4\xfa\xac\x6f\x24\x49\xc2\xd4\xf1\ -\x23\x30\xab\x70\x3c\x66\x4d\x1b\x87\x11\x05\xb9\x9a\xc7\x90\x99\ -\x9e\x82\x05\xb7\x5f\x8d\x05\xb7\x5f\x8d\xf2\xd3\x75\x78\xff\xb3\ -\xad\xd8\xb2\xb3\x14\x72\x84\x77\x69\xed\xaf\xaa\xf2\x63\xf0\x78\ -\xdc\x41\xbb\x1b\x40\x51\xa5\x21\xc0\xd7\x09\x80\xac\x22\x43\xcf\ -\x60\x44\x84\x2f\x81\xf3\x1f\x19\x03\x80\xac\x8c\x54\xfc\xf0\xd1\ -\xbb\x70\xed\xec\xa9\xbd\xbf\xb8\x1f\xda\x3a\xcd\x78\xf5\xdd\xcf\ -\xb1\x61\x5b\x31\x2f\xf7\x07\x81\x61\xf9\xd9\xb8\xf5\xea\x4b\x71\ -\xe3\xd5\xd3\x91\x91\x96\xa2\x77\x38\xdf\x18\x3d\x2c\x0f\xbf\x79\ -\xfe\x61\x3c\xfb\xe0\x6d\x58\xf4\xfe\x5a\xac\xe7\xdf\x97\x3e\xf3\ -\x78\xdc\xa8\x39\x5d\x81\x11\xa3\xfd\x7a\xc0\xae\xdf\xa8\x84\xc1\ -\xc0\xd7\x09\x80\x47\x81\xff\x4b\x8a\xfb\xc0\x68\xf4\x7f\xe7\xb2\ -\x50\x22\xcb\x9c\x00\x44\xb2\xe8\xa8\x28\x7c\xe7\xce\xb9\x78\x74\ -\xfe\xf5\x88\x8d\xf1\xff\x1d\x83\xac\x28\x58\xf9\xc5\x0e\x2c\x59\ -\xb1\x0e\x76\xbb\xd3\xef\xd7\x67\xbe\x4b\x4e\x8c\xc7\xf5\x73\xa6\ -\xe1\xe6\x6b\x2e\x0d\x58\x41\xa7\xbf\x0c\xce\x48\xc3\xaf\x7f\xf4\ -\x10\xe6\xdf\x72\x05\xfe\xb2\xf8\x13\x1c\xad\xe0\x47\x03\x7d\x51\ -\x55\x7e\x34\x68\x13\x00\x97\x42\x69\xc0\xd7\x09\x80\x4a\xd0\xb5\ -\x5c\x31\xd2\xb7\xc1\x79\x38\x01\x88\x58\x73\x66\x4c\xc4\xf3\x4f\ -\xcc\x43\xde\xe0\xc0\x2c\xc2\x95\x94\x55\xe0\xa5\xc5\x2b\x51\x55\ -\xd3\x18\x90\xeb\x33\xdf\x0c\xcb\xcf\xc6\x83\x77\x5e\x83\xeb\xaf\ -\x2c\x0c\xb9\xce\x9f\x13\x46\x0d\xc5\xeb\xff\xf3\x3c\xde\x5e\xb9\ -\x11\x6f\x7d\xbc\x01\x8a\x12\xd9\x9d\x5b\x7d\x55\x55\x7e\x4c\xef\ -\x10\xba\x25\xab\x48\x00\xbe\x5d\x01\xd0\xf5\x37\xd2\x10\xe9\x2b\ -\x00\xfc\x08\x20\xe2\x14\xe4\x66\xe1\xf9\xc7\xe7\x61\x56\xe1\xf8\ -\x80\x5c\xbf\xa5\xdd\x84\x57\xde\xfe\x14\x9b\x76\x1e\x08\xc8\xf5\ -\x99\x6f\xa6\x4d\x1c\x85\x07\xee\xba\x06\x97\x4f\x1b\x1f\xd2\x0d\ -\xcf\x24\x49\xc2\x13\xf7\xdd\x84\x19\x93\xc7\x60\xe1\x4b\xef\xa2\ -\xa9\xb5\x43\xef\x90\x82\x5e\x67\x7b\x2b\xda\x5b\x9b\x91\x9e\x91\ -\xa5\x77\x28\x17\xf1\x28\x22\x1a\x00\x8c\x2b\xca\x28\xba\xa6\x8d\ -\x84\xd9\x25\x02\x72\xd2\x9f\x2f\x8c\x01\x38\xbc\x24\x94\x28\x9c\ -\x00\x44\x8c\xf8\xd8\x18\x3c\x76\xef\x0d\x58\x70\xfb\xdc\x80\xf4\ -\x7f\xf0\xc8\x0a\x3e\x5a\xf3\x15\xde\xfa\x78\x03\xec\xff\x9f\xbd\ -\xfb\x8e\x8e\xb3\x3a\x13\x3f\xfe\xbd\xef\x14\xf5\x51\xb1\x7a\x97\ -\x6d\xc9\xb6\x64\xcb\x96\x2c\xdb\xe0\x8a\x0b\x60\x1b\x07\x5c\x08\ -\x09\x25\x05\x30\x21\x85\x24\x40\x36\xbb\xd9\x24\xfb\x4b\x76\xb3\ -\x9b\xb6\xd9\x40\x08\x29\x24\xb4\x84\x90\x50\x8d\xe9\x60\x70\x01\ -\x8c\x71\x95\x4d\x71\xc5\x4d\xdd\x92\x65\xf5\xae\x99\xf7\xfe\xfe\ -\x10\x26\x36\xc8\xb6\xca\xbc\x65\x34\xf7\x73\x4e\xce\xf1\x91\x67\ -\xee\x7d\x82\x47\x73\x9f\xf7\x96\xe7\x76\x75\xfb\xbd\x7d\xe5\xc2\ -\x84\x10\xcc\x9b\x31\x89\xeb\x97\x2f\x64\xc2\xd8\x4c\xab\xc3\xf1\ -\xab\x49\xe3\x72\xb8\xef\x67\xdf\xe6\xbb\xff\xf3\x67\x3e\x3c\xae\ -\x36\x91\x5e\xc8\xf1\x23\xfb\x6d\x99\x00\xf4\x78\x71\xdc\x27\xa5\ -\xcb\x99\xe7\x21\x61\x6c\x8c\xa4\xa3\x47\x72\xe8\x94\xa0\xac\x41\ -\x60\x76\x1e\x10\xec\x4b\x00\xed\x9d\xea\x8b\x7a\xa4\x13\x42\xb0\ -\x68\x76\x11\x5f\xff\xc2\x95\x24\xc4\x19\xb3\xe1\x6b\xc7\xbb\x87\ -\xb8\xeb\xc1\x35\x94\x57\xd5\x1a\xd2\xbe\x72\x61\x53\x27\xe5\xf2\ -\xf5\x1b\x3e\xc3\xb8\x31\x19\x56\x87\x62\x98\xf8\xd8\x68\x7e\xf7\ -\x93\x6f\xf2\x83\x5f\x3d\xc8\x8e\x77\x0f\x59\x1d\x8e\xad\x55\x1c\ -\xfd\x90\xe2\x19\xf3\xac\x0e\xe3\x53\x7c\x12\x92\x0f\x32\xda\x89\ -\x46\x22\x40\xb8\x1b\xa6\xa4\x48\xd2\x3d\x92\xd2\x6a\x8d\x8e\x1e\ -\xf3\x82\x09\xf6\x25\x80\xb6\xb6\x0e\xab\x43\x50\x0c\x34\x36\x2b\ -\x8d\x3b\x56\xaf\x60\xf2\x04\x63\x6e\x09\x3b\xd9\xd0\xcc\x1f\x1f\ -\x7d\x81\x57\xdf\xd8\x69\x48\xfb\xca\x85\xe5\x64\x24\x73\xe3\x35\ -\x8b\x59\x70\xf1\x64\xab\x43\x31\x45\x78\x58\x08\xbf\xfc\xf7\xaf\ -\xf0\x6f\x3f\xff\x33\xdb\xf7\x1c\xb4\x3a\x1c\xdb\xaa\x28\x3b\x82\ -\x94\xd2\x96\xcb\x3f\xed\x9d\x4c\x70\x22\xbc\x09\xc8\x7f\x16\xa2\ -\x89\x8f\x80\x79\x39\x3a\x5b\xca\x34\x9a\x4d\xda\x30\xec\x76\xdb\ -\xf3\xac\xa4\x59\x5a\x54\x02\x30\x22\x79\x22\xc3\x59\x7d\xed\x52\ -\x96\x5f\x7a\xb1\x21\xc5\x9e\x7a\xbc\x5e\x1e\x7b\x76\x23\x7f\x5d\ -\xf3\x3a\x5d\xdd\x26\x66\xec\xca\xc7\x12\xe3\x63\xf8\xea\x75\xcb\ -\x58\x34\xbb\xd8\x90\x4a\x8d\x76\xe6\x72\x3a\xf8\xaf\xef\xdc\xc8\ -\xea\xef\xff\x81\xca\x0a\x75\x42\xa0\x3f\x5d\x9d\x1d\xd4\xd7\x55\ -\x93\x90\x64\x69\xa9\x9d\x7e\xf5\xf8\xf4\x31\x4e\x9f\x14\xb1\x9f\ -\xfc\xd8\x86\x38\x61\x76\x8e\xce\xdb\xc7\x35\x9a\x4c\x28\x12\xe6\ -\x0e\xf1\x7f\xa5\xab\x40\xd2\xd2\xae\x12\x80\x91\x44\xd3\x04\xcb\ -\x16\x5e\xc4\xad\xd7\x5e\x41\xb4\x27\xc2\x90\x3e\xde\xd9\xbd\x9f\ -\xdf\x3c\xb0\x86\xca\x13\xf5\x86\xb4\xaf\x9c\xdf\xe9\xa3\x9b\x5f\ -\x58\xb1\x88\xb0\xd0\xe0\x7d\x80\x39\xd2\x18\xca\xf2\xeb\x6e\xe5\ -\xd1\x3f\xff\x9a\xc6\x06\xf5\x59\xec\x4f\xc5\xf1\xc3\xb6\x4c\x00\ -\xa4\x24\xc9\x89\x14\x31\xfd\x15\xe2\x73\x69\x70\x71\xa6\xce\x1b\ -\xc7\x8c\x5f\x0e\x08\x0d\x0d\x35\xb6\x03\x9b\x6b\x6d\x53\xa5\x58\ -\x47\x8a\x49\xe3\x72\xb8\xe3\xe6\x95\xe4\x8d\x4e\x37\xa4\xfd\xea\ -\xda\x53\xdc\xf3\xf0\x33\x6c\xde\xb1\xd7\x90\xf6\x95\x0b\x33\xfa\ -\xe8\x66\xa0\xa8\x6a\x16\x1c\x6b\x14\x84\x86\x47\xf0\x99\xcf\xdd\ -\xc4\xdf\xff\x7c\x17\x5e\x6f\xaf\xd5\x61\xd9\x4e\xe5\xf1\x23\xb6\ -\xdc\x07\x20\x25\xf1\x4e\xa1\x11\x7b\xae\x5d\x7f\x21\xce\xbe\x24\ -\xe0\xad\x63\x1a\x3d\x06\x6e\x54\x0f\xf6\x19\x80\x56\xb5\x04\x10\ -\xf0\xe2\x62\x3d\xac\xbe\x66\x31\xcb\x16\x5e\x64\xc8\x54\x70\x77\ -\x4f\x2f\x4f\xbd\xf4\x26\x0f\x3f\xb5\x8e\xce\x2e\x35\xdd\x6f\x85\ -\xf4\xe4\x78\xbe\x75\xe3\x8a\x8f\xeb\xe7\x07\xb3\x2e\x2f\xbc\x5b\ -\xf3\xcf\xcf\x79\x62\x72\x1a\x0b\x96\xae\x64\xdd\x73\x8f\x5b\x18\ -\x95\x3d\x55\x55\x1c\xb3\x3a\x84\x7e\x49\x4d\x8b\x75\x22\x39\xef\ -\x96\xe4\xa8\x10\x98\x91\xd1\xb7\x27\xc0\x67\xd0\xf1\x80\x90\x90\ -\xe0\x9e\x01\x38\xd5\xdc\x6a\x75\x08\xca\x10\x39\x1d\x0e\xae\x5e\ -\x3a\x87\x9b\x3e\x7b\x39\xe1\xe1\xc6\x7c\x8e\x37\xef\xd8\xcb\x3d\ -\x0f\x3f\x43\x75\xed\x29\x43\xda\x57\xce\x2f\x3c\x34\x84\x2f\xae\ -\xba\x94\xcf\x7d\xc6\x98\xa3\x9b\x81\xe8\xc3\x7a\xf1\xa9\x87\xc2\ -\xc2\xa9\x33\x29\x3b\x7a\x88\x83\x1f\xa8\xda\x13\x67\x6a\x6f\x6d\ -\xa1\xb5\xb9\x89\xa8\xe8\x18\xab\x43\x39\x8b\xae\xcb\x18\x27\x82\ -\x98\x0b\x9d\xfb\x1b\x15\x01\x85\x29\x92\xdd\xd5\xc6\x6c\x72\x71\ -\x07\xf9\x12\x40\x67\x67\x37\x6d\x1d\x9d\x86\xdc\xfa\xa6\x18\xc7\ -\xe8\x4b\x7b\xaa\x6a\xeb\xf9\xcd\x03\xcf\xb0\xa5\x74\x9f\x21\xed\ -\x2b\x17\x36\x73\x6a\x3e\x77\xae\x5e\x45\x72\x42\x9c\xd5\xa1\xd8\ -\x4a\x6d\x5b\xff\x63\xc1\xc2\x25\xab\x38\x7e\xf8\x20\xdd\x5d\x6a\ -\x56\xf3\x4c\x35\x55\x65\xb6\x4b\x00\x7c\x92\xe8\x0b\xce\x00\x9c\ -\x96\x15\x2b\x69\xeb\xe9\xcb\xfc\xfc\x2d\x34\x54\x0d\x7c\x35\x75\ -\x0d\xe4\x66\xdb\x6f\xa3\x88\xf2\x69\x29\x89\x71\x7c\xf3\x4b\xcb\ -\x99\x3b\x63\x92\x21\xed\x77\x75\xf7\xf0\xd7\x35\xaf\xf3\xd8\xb3\ -\x1b\xe9\x51\x37\x45\x5a\x62\x4c\x56\x2a\x77\xdc\xbc\x92\x29\xf9\ -\xc6\x1c\xdd\x0c\x74\xfa\x39\x1e\x1a\xc3\x23\xa3\x98\xb3\xf0\x0a\ -\x5e\x7f\xf1\x49\x73\x03\xb2\xb9\x9a\xaa\x32\xf2\xf2\xed\x75\x44\ -\x54\xd7\x45\xa4\x13\x44\x24\x03\x2c\xfd\x93\x9f\x28\xe9\xea\x85\ -\x8a\x66\xff\x26\x01\xa1\xa1\xc6\xec\x94\x0e\x24\x27\x4e\x36\xaa\ -\x04\xc0\xe6\x42\xdc\x2e\xae\x5f\xbe\x80\xeb\x97\x2f\x24\xc4\x6d\ -\x4c\xed\x8a\x4d\x5b\xdf\xe3\xb7\x0f\xaf\x55\xa5\x56\x2d\x12\x15\ -\x11\xce\x2d\xd7\x2e\x61\xf9\x65\x33\x0d\x39\xba\x39\x52\x24\x45\ -\x4a\x8e\x35\xf4\x3f\x0e\x4c\x9e\x36\x8b\xbd\x7b\xb6\x53\x53\xa5\ -\x8e\x06\x9e\x76\xa2\xb2\xdc\xea\x10\x3e\xc5\x27\x09\x73\x02\xe1\ -\x03\x7d\x83\x10\x50\x9c\x26\x71\x68\x70\xbc\xd1\x7f\x49\x40\x68\ -\x78\x38\x9a\xa6\xa1\xeb\xc1\x7b\xc9\xc4\x89\x93\x0d\x56\x87\xa0\ -\x9c\xc7\xbc\x8b\x0a\xb9\xed\x8b\x57\x91\x92\x68\xcc\x54\x70\x79\ -\x55\x2d\x77\x3d\xb8\x46\x55\x56\xb3\x88\x19\x47\x37\x47\x92\xdc\ -\x51\x92\xb2\x06\x41\x7f\xdf\xd8\x42\x08\xe6\x2c\x5a\xc6\x13\x7f\ -\xf9\x9d\xe9\x71\xd9\x55\x6d\x4d\x05\x48\xd9\x37\x88\xda\x84\xd7\ -\x27\x43\x9d\xa0\x87\xd1\xdf\x39\xc0\x73\x10\x02\x26\xa7\x4a\xa2\ -\x42\x60\x7f\x9d\xc0\xeb\x87\x31\x5b\x08\x41\x58\x44\x24\xed\xad\ -\x2d\xc3\x6f\x2c\x40\xd5\x9e\x54\x4f\x7c\x76\x94\x95\x9e\xc4\xed\ -\x37\xae\x64\xda\xe4\x3c\x43\xda\xef\xe8\xea\xe6\xe1\xa7\xd6\xf1\ -\xe4\x0b\x6f\xa8\x5b\x21\x2d\x32\x31\x2f\x9b\x3b\x6e\x5e\x39\xa2\ -\xcb\xf7\xfa\x5b\xb8\x1b\xd2\x63\x24\xe5\x4d\xfd\x8f\x1d\x99\xa3\ -\xf3\x48\xcf\x1c\x43\x65\xf9\x11\x93\x23\xb3\xa7\x9e\xee\x2e\x9a\ -\x9b\x1a\x88\x8e\x1d\x65\x75\x28\x1f\xd3\x25\x6e\x27\x88\x01\xcf\ -\x00\x9c\x26\x80\x31\xa3\x24\xc9\x51\x92\x7d\xb5\x82\x13\xad\x62\ -\xd8\x27\x04\xc2\x83\x3c\x01\x28\xaf\xae\xb3\x3a\x04\xe5\x0c\x61\ -\xa1\x6e\xae\xbd\x72\x3e\x37\xac\x5c\x64\xd8\xf5\xad\x6f\xef\xdc\ -\xcb\xaf\xef\x7f\x5a\x4d\xf7\x5b\xc4\x13\x15\xc1\x97\xaf\xbe\x8c\ -\xab\x97\xcc\x09\xba\x2a\x7e\xfe\x90\x17\x7f\xee\x04\x00\x60\xe6\ -\x82\xc5\x3c\xf1\xb0\x9a\x05\x38\xed\x64\x6d\x8d\xcd\x12\x00\xe1\ -\x1c\xd4\x12\xc0\x27\x45\xb8\x61\x5a\x86\xc4\xeb\x93\x34\x76\x09\ -\x9a\x3a\xa1\xa5\x1b\x5a\xbb\x04\x2d\xdd\xe7\xde\x28\xd2\x9f\xf0\ -\x88\xa8\xa1\x86\x31\x22\x1c\x29\xab\xb1\x3a\x04\x85\xbe\xd9\xa8\ -\xcb\xe7\x4e\xe5\xeb\x37\x7c\x86\xb8\x58\x8f\x21\x7d\x1c\xab\x38\ -\xc1\xaf\xef\x7f\x8a\xdd\x7b\xd5\xd3\x91\x15\xcc\x38\xba\x19\x0c\ -\x22\x43\xc0\x13\x0a\x2d\xe7\x28\x19\x9f\x99\x93\x47\x5a\x46\x8e\ -\x6d\xcf\xc1\x9b\xad\xbe\xb6\x9a\xb1\xe3\x27\x5a\x1d\xc6\xc7\xa4\ -\xc4\xe1\x04\x86\xbd\x05\xdf\xe9\x80\x84\x08\x49\xc2\xc7\x4b\x67\ -\x12\xaf\x0f\x6a\xda\x04\xd5\x2d\x50\xdb\x2a\x2e\x98\x0c\x04\x7b\ -\x02\x50\x5b\xdf\x48\x6b\x7b\x07\x51\x11\x43\xce\xc7\x94\x61\xca\ -\xcb\x49\xe7\xf6\x9b\x57\x50\x38\x7e\xb4\x21\xed\x77\x76\xf5\xf0\ -\x8f\xe7\x36\xf0\xc8\x9a\xd7\xd5\x74\xbf\x45\x8c\x3e\xba\x19\x6c\ -\x22\xdc\x92\x96\xae\x73\xcf\x02\x4c\x9e\x36\x4b\x25\x00\x1f\xa9\ -\xaf\xab\xb6\x3a\x84\xb3\x09\x34\x27\x60\x48\x0a\xec\x74\x40\x46\ -\xb4\x24\x23\x1a\xba\xbd\x92\x8a\x66\x41\x6d\x2b\x34\x74\xf4\xbf\ -\x5c\x10\x11\x11\x69\x44\x18\x01\xe5\x48\x59\x8d\x3a\x76\x64\x81\ -\x68\x4f\x04\xb7\x5e\x7b\x85\x61\x55\xfc\xa4\x94\xac\x7b\x73\x17\ -\xbf\xfb\xdb\xf3\x34\x34\x06\xef\x32\x97\x95\x92\x13\xe2\xb8\xed\ -\x4b\x57\x71\xc9\x45\x85\x56\x87\x32\xa2\xf4\x5e\x20\x8f\xcd\x2b\ -\x98\xc2\x86\x57\x9e\xa1\xab\xa3\xdd\x9c\x80\x6c\xec\x64\x9d\xbd\ -\x66\x79\x75\xbd\x2f\x01\x30\x66\x81\xf3\x0c\x21\x4e\x18\x3b\x4a\ -\x32\x76\x14\xf8\xa4\xa4\xa9\xb3\x6f\x99\xa0\xad\xa7\x6f\xc9\xa0\ -\xad\x5b\x10\xe9\xb1\x57\x91\x04\x2b\xa8\x04\xc0\x5c\x9a\xa6\xb1\ -\xfc\xb2\x99\xac\xfe\xfc\x12\x3c\x91\xc6\xcc\xbc\x1c\x2e\xab\xe2\ -\xd7\xf7\xaf\xe1\xbd\xfd\x47\x0d\x69\x5f\x39\xbf\x10\xb7\x8b\xeb\ -\xae\x5a\xc0\xf5\xcb\x17\x10\x1a\x12\xbc\x97\xf6\x18\xa5\xa3\xe7\ -\xfc\x09\xb3\xd3\xe9\x62\xe2\x94\xe9\xec\xdc\xb2\xd1\xa4\x88\xec\ -\xab\xf1\xd4\x49\x74\x9f\x0f\xcd\x61\x8f\x6a\x92\x52\x22\x9c\x12\ -\x9c\x66\x6e\x7f\x71\x08\x18\x15\x0e\xa3\xc2\xcf\x9c\x06\x90\x68\ -\x27\x63\xd8\xf4\xaa\x89\x81\xd8\xd0\xc1\xa3\x15\x56\x87\x10\x34\ -\x26\x4f\x18\xc3\x1d\xab\x57\x30\x36\xcb\x98\xda\x0b\x6d\x1d\x9d\ -\xfc\xf9\x1f\x2f\xf1\xcc\xab\x5b\x82\xfa\x78\xab\x95\xe6\x4e\x9f\ -\xc8\x6d\x5f\x5a\x4e\x6a\x92\x7d\x36\x5e\x8d\x24\x3d\x3e\xe8\x18\ -\xc0\xdd\x3f\x93\x8a\x2e\x52\x09\x00\xa0\xfb\x7c\x34\x37\x35\x10\ -\x3b\x2a\xc1\xea\x50\x80\xbe\x3d\x7a\x4e\x01\xb6\x48\x47\x52\x12\ -\x07\x54\x90\x70\x44\x7b\x77\x9f\x7a\x4a\x34\x5a\x42\x5c\x34\x5f\ -\xff\xc2\x95\x2c\x9a\x5d\x84\x30\xe0\x4c\xae\x94\x92\x97\x37\xed\ -\xe0\x0f\x7f\x7b\x81\x46\x75\xc7\x83\x25\x32\x53\x13\xb9\xfd\xe6\ -\x95\x4c\x9f\x3c\xce\xea\x50\x46\xb4\xd6\xee\x81\xbd\x6e\x54\x62\ -\x32\x71\x09\x49\x34\x9c\xac\x35\x36\xa0\x00\xd0\xd8\x70\xd2\x36\ -\x09\x00\x20\x9c\xd8\x24\x01\x48\x1c\x15\x6b\x75\x08\x96\xab\xaa\ -\xad\xa7\xae\xbe\x89\xc4\x78\xb5\x1c\xe2\x6f\x4e\x87\x83\x15\x97\ -\xcf\xe2\x96\xcf\x2f\x31\x6c\xe7\xf7\xa1\xa3\x95\xdc\xf5\xc0\x1a\ -\xde\x3f\xa8\x36\x3d\x59\xc1\x8c\xa3\x9b\xca\x3f\xf5\xf8\x06\x9e\ -\x40\xe7\xe5\x4f\x66\xeb\x1b\xeb\x0c\x8c\x26\x30\x34\x35\x9c\xb4\ -\x3a\x84\x8f\xf9\xa4\xb4\x4f\x02\x30\x2a\x26\x0a\xa7\xc3\x81\xd7\ -\x17\xdc\xbb\xa3\xdf\xdd\x7f\x94\x4b\xe7\x14\x5b\x1d\xc6\x88\x32\ -\x75\x52\x2e\x77\xdc\xb4\x92\xec\x8c\x64\x43\xda\x6f\x69\xeb\xe0\ -\xa1\x27\x5e\xe5\xe9\x57\x36\xab\xe9\x7e\x0b\x08\x21\x98\x7f\x51\ -\x21\xb7\x7d\x69\xb9\x4a\x9e\x4d\x14\xe2\x90\x0c\xb4\x88\x5c\xee\ -\xf8\x42\x95\x00\xd0\xb7\x0f\xc0\x2e\xa4\x14\xd8\x26\x01\xd0\x34\ -\x8d\xf8\xb8\xe8\xa0\x2f\x89\xbb\x7b\xdf\x61\x95\x00\xf8\x49\x5a\ -\x52\x3c\xdf\xba\x71\x39\xb3\x4a\x0a\x0c\x69\xdf\xe7\xd3\x59\xbb\ -\x6e\x0b\xf7\x3f\xf6\x32\xad\xed\xea\xf6\x33\x2b\x18\x7d\x74\xb3\ -\xa7\xb7\x97\xc7\x9e\xdb\xc4\x94\x82\x31\x86\xf5\x11\xa8\x62\xc3\ -\x20\xcc\x09\x9d\x03\xb8\xaf\x2a\x29\x25\x1d\x4f\x4c\x1c\x2d\x4d\ -\xc1\xfd\xfd\x6e\xa7\x04\x40\xc7\x84\x13\x00\x83\x91\x9a\x18\x17\ -\xf4\x09\xc0\xce\xf7\xac\xab\x05\xaf\x03\x75\xad\x82\x50\x97\x24\ -\x26\x80\xeb\xa3\x84\x84\x38\xf9\xca\xb5\x4b\xf9\xfc\x95\x97\xe0\ -\x76\x19\x73\x69\xcf\xee\xbd\x87\xb9\xfb\xc1\x67\x38\x52\x66\xb3\ -\xb3\xbd\x41\xc2\x13\x15\xc1\x57\xae\x5d\xca\x95\x8b\x2e\x32\xec\ -\xd2\x9e\xb7\x77\xee\xe5\x9e\x87\xd6\x52\x55\x5b\xcf\x5d\xff\xf1\ -\x35\x43\xfa\x08\x64\x42\x40\x7e\xb2\x64\x57\xe5\x00\x66\x01\x84\ -\x20\x6b\xcc\x38\xde\xdf\xf5\x8e\xf1\x81\xd9\x58\x73\x43\xbd\xd5\ -\x21\x9c\xc5\x09\xf8\xb0\x49\x22\x90\x9e\x9a\x40\xe9\xde\xc3\x56\ -\x87\x61\xa9\xea\xda\x53\x1c\x3a\x56\x49\x5e\x4e\xba\xa9\xfd\x76\ -\xf6\xc2\xd6\x72\x8d\xe6\xae\xbe\x29\xd5\x29\xa9\x92\xac\x98\x61\ -\xd6\x77\xb6\x48\xe1\xf8\xd1\x86\x3d\xad\xd5\x37\x36\xf3\x87\xbf\ -\xbd\xc0\xba\x37\x77\x21\x65\x60\xfe\xf7\x09\x64\x9a\x26\xb8\x74\ -\xce\x54\xbe\xf9\xa5\xab\x88\xf1\x18\x53\x3b\xa4\xf2\x44\x3d\xf7\ -\x3c\xf8\x0c\x5b\x4a\xf7\x19\xd2\xfe\x48\x92\x11\x2d\xa9\x6f\x83\ -\xb2\xf3\x94\x04\x3e\x2d\x33\x7b\x6c\xd0\x27\x00\x2d\xcd\x4d\xb6\ -\xb9\x14\x48\xc3\x66\x09\x40\x46\x8a\x6d\x76\x47\x5a\xea\xcd\x6d\ -\xef\x9b\x9a\x00\x34\x77\xc1\xd6\x32\xed\xe3\xa9\x3c\x29\xe1\x60\ -\x9d\x08\xd8\x04\xc0\x08\x5e\x9f\x8f\x35\xaf\xbc\xcd\x9f\x1f\x7b\ -\x89\xce\xce\x01\x6e\x7f\x56\xfc\x6a\xc2\xd8\x4c\xee\x58\xbd\x92\ -\xfc\xb1\x59\x86\xb4\xdf\xd5\xdd\xc3\xdf\x9f\xdd\xc0\xdf\xd6\xac\ -\xa7\xc7\x3b\x80\x79\x6d\x05\x80\xc9\x69\x92\x2e\x5f\x5f\xc5\xd7\ -\xf3\x49\xcf\x1e\x6b\x52\x44\xf6\xe5\xf3\x79\xe9\xe8\x68\xb3\x45\ -\xe5\x5b\x21\xe4\xc7\x09\x80\x2d\x64\xa4\x24\x5a\x1d\x82\x2d\xbc\ -\xb1\xed\x3d\x56\x7f\x7e\x89\x29\x7d\x55\x34\x09\xde\x3d\x21\xf8\ -\x64\x65\xda\xa8\xd0\xf3\x0f\xfe\x9d\x5e\x68\xec\x10\x44\x87\x4a\ -\x22\x46\x78\x7d\x95\x77\x76\xef\xe7\x9e\x07\x9f\xa1\xa2\xc6\x3e\ -\xeb\x77\xc1\x24\x2e\xd6\xc3\x37\x6e\xf8\x0c\x97\xcd\x9d\x6a\xd8\ -\xd1\xcd\xd7\x37\xef\xe6\xf7\x8f\x3c\xc7\xc9\x86\x66\xbf\xb7\x3f\ -\xd2\x69\xc0\xf4\x0c\xc9\xce\x0a\xa8\x39\x4f\x12\x10\xe5\x89\x21\ -\x3a\x76\x14\xcd\x8d\xa7\xcc\x0b\xce\x86\x5a\x9a\x1b\x6d\x91\x00\ -\x38\x84\x90\x4e\x09\x83\x38\xcc\x61\xac\xf4\x64\x55\xb0\x03\xfa\ -\x2e\x8b\x29\xab\xac\x25\x2b\x3d\xc9\xb0\x3e\xba\xbd\xb0\xa7\x5a\ -\xf4\xfb\x0b\xeb\x76\x40\x61\x72\xff\x09\x80\x94\x70\xb0\x5e\x70\ -\xa8\xae\xef\x2e\x70\x81\x20\x29\x4a\x32\x2e\x41\x12\x3b\xec\x5b\ -\x25\xec\xa5\xaa\xb6\x9e\x7b\x1e\x5a\xcb\xdb\x3b\xf7\x5a\x1d\x4a\ -\x50\x72\x38\x34\xae\x5e\x32\x87\x9b\xaf\x59\x6c\xd8\xd1\xcd\xc3\ -\x65\x55\xdc\x75\xff\x33\xbc\xbb\x5f\x5d\xcc\x34\x1c\x0e\xd1\x97\ -\x04\xbc\x5b\x03\xc7\x1b\xcf\x3d\xa2\xa4\x67\x8d\x09\xfa\x04\xa0\ -\xb5\xb9\x89\xe4\xd4\x4c\xab\xc3\x00\x90\x4e\x01\xb6\x99\xeb\x4a\ -\x4d\x89\x47\xd3\x34\x75\x94\x0a\x78\x7e\xfd\x56\x6e\xfb\xd2\x55\ -\x7e\x6f\x57\x07\x8e\x9d\x12\x1c\x3c\x29\xe8\x39\xc7\xdc\xcf\x94\ -\xd4\xfe\x9f\xea\xa5\x84\xd2\x6a\x41\xc5\x19\xeb\x7d\x12\x38\xd1\ -\x2a\xa8\x6d\x15\x64\x44\x4b\x0a\x92\x25\x21\xb6\x58\x50\x1a\xba\ -\xce\xae\x1e\x1e\x79\xe6\x75\x1e\x7b\x6e\x13\x3d\xbd\x03\x28\x75\ -\xa6\xf8\x5d\x51\xc1\x18\xee\x5c\x7d\x35\x39\x06\x1e\xdd\xbc\xff\ -\xb1\x97\x59\xbb\x4e\x55\x6a\xf4\x17\x21\xfa\xbe\x3b\x22\x43\x60\ -\x6f\xad\xa0\xbf\x2d\x32\x49\xa9\xe9\xec\xdd\xb3\xdd\xfc\xe0\x6c\ -\xa4\xa5\xd9\x1e\xd7\x7f\x6b\xa2\x6f\xed\xdf\x36\x09\x80\xdb\xe9\ -\x24\x3d\x39\x9e\xf2\xea\x3a\xab\x43\xb1\xdc\x4b\x9b\x76\xf0\x95\ -\xeb\x96\xfa\x6d\x17\x7b\x5b\x0f\x54\x35\x0b\x8e\x37\x88\xf3\x1e\ -\xdb\x09\x77\x43\xaa\xe7\xd3\xbf\xb9\xba\x84\xed\x15\x82\x13\xe7\ -\x98\xe2\x93\x40\x79\xb3\xa0\xba\x4d\x30\x3e\x41\x32\x7a\x94\xc4\ -\x98\xbd\xd9\xc6\xda\xf0\xce\xbb\xdc\xfb\x97\xb5\xd4\xd5\x37\x59\ -\x1d\x4a\x50\x4a\x88\x8b\xe6\x1b\x5f\xba\x8a\x45\xb3\x8a\x0c\x69\ -\x5f\xd7\x25\x2f\xac\xdf\xca\x7d\xff\x78\x91\xe6\x16\x75\x41\x8d\ -\x11\xc6\x8e\x92\x44\xb8\x60\x67\x95\xc0\xf7\x89\xdc\x2a\x21\xd1\ -\x98\xd2\xdb\x81\xa4\xb5\xc5\x1e\xdf\x2d\x42\x20\x9d\xc0\x39\x6e\ -\x73\xb6\xc6\xe8\xcc\x64\x95\x00\x00\x2d\xad\xed\x6c\x7a\xe7\x3d\ -\x2e\x9b\x3b\x75\x50\xef\xd3\x81\xf6\x8f\x2e\x58\x6a\xeb\xe9\x2b\ -\xd7\xd9\xd0\xd1\xf7\xe7\x81\xf0\xe9\xe0\x93\x7d\x53\x7a\xa7\x75\ -\xf4\xc2\xf6\x0a\x8d\xa6\xce\x0b\xbf\xdf\xeb\x83\x0f\x4e\xf4\x25\ -\x0a\xd3\xd2\xf5\x80\x99\x0d\x28\xaf\xae\xe3\xee\x07\xd7\xb0\x7d\ -\xcf\x41\xab\x43\x09\x4a\x66\x54\x6a\x3c\x70\xa4\x9c\xbb\xee\x7f\ -\x86\xbd\x1f\x1e\x37\xa4\x7d\xe5\x9f\x52\x3c\x92\xd9\x2e\xc9\xd6\ -\x72\x8d\xee\x33\x1e\x38\x12\x92\x53\xad\x0b\xca\x26\x3a\xda\xed\ -\x51\x22\x5c\xd3\xd0\x9d\xc0\x00\xbe\xd6\xcd\x33\x3a\x33\x85\x4d\ -\x5b\xdf\xb3\x3a\x0c\x5b\x58\xbb\x6e\xcb\x80\x12\x80\x86\x0e\xa8\ -\x6c\x16\xd4\x77\x08\xda\xba\xfa\x92\x80\xa1\xea\xf6\xc2\xd6\x32\ -\x41\x7a\x34\x38\xb4\xbe\xb6\x8f\x37\x0a\xf4\x41\x1e\x08\xa8\x6f\ -\x87\x37\x8e\x69\xcc\xce\xd2\x09\xb7\xf1\x26\xc1\xf6\x8e\x4e\x1e\ -\x78\xfc\x15\x9e\x7e\x65\x33\xbe\x4f\x3e\xae\x28\xa6\x28\x29\xcc\ -\xe3\xce\x9b\x57\x92\x99\x66\xcc\x9e\x97\x86\xc6\x16\x7e\xff\xb7\ -\xe7\x79\x55\x1d\xdd\x34\x55\x6c\x18\xcc\xcd\xd1\xd9\x52\xa6\xd1\ -\xfe\xd1\x03\x48\x68\x58\x38\x91\x9e\x68\xda\x5a\x82\x77\xb3\x65\ -\x47\x9b\x3d\x12\x00\x64\x5f\x02\x60\xab\x12\x66\x63\x32\x55\x86\ -\x78\xda\x7b\x07\x8e\xb2\x7b\xef\x61\x8a\x0a\xfa\x3f\x3e\xd3\xda\ -\x0d\x7b\x6a\x34\x4e\xf9\x79\x26\xf3\x64\xbb\xe0\xa4\x1f\xda\xec\ -\xe8\x81\xb7\x8e\x6b\xcc\xca\xd6\x89\xb4\x59\x12\x20\xa5\xe4\xd5\ -\x37\x77\xf1\xfb\x47\x9e\xa3\xa1\xc9\x26\xbf\x90\x41\x26\x21\x2e\ -\x9a\x5b\xaf\x5f\xc6\xe2\x79\x25\x86\xb4\x7f\xfa\xe8\xe6\x03\x8f\ -\xbf\x42\x7b\x87\xad\x9e\x73\x82\x46\x84\xbb\x2f\x09\x78\xa7\xfc\ -\x9f\x33\x88\x09\x49\x69\x41\x9d\x00\x74\xb6\xb7\x59\x1d\x02\x00\ -\x42\xe0\x73\x82\xec\x18\x68\x3d\x67\x33\x8c\xce\x4a\xb1\x3a\x04\ -\x5b\xb9\xff\xb1\x57\xf8\xdd\x4f\x6e\xfb\xd4\xcf\x4f\xb5\xc3\x3b\ -\x15\xda\xa7\x8e\xef\xd9\x4d\x67\x2f\xbc\x71\x44\x63\x6a\xba\x24\ -\x39\xca\x98\xa7\xaf\x2e\x6f\xdf\xfe\x86\x96\xee\xbe\xe5\x0f\xaf\ -\x14\x68\x02\x22\x5d\x92\x84\x48\x48\x8d\x96\x67\x2d\x69\xec\x3f\ -\x5c\xce\x5d\x0f\xac\x61\xdf\x87\x65\x86\xc4\xa3\x9c\x9f\xdb\xe9\ -\xe4\xf3\x57\x5d\xc2\x17\x56\x5c\x4a\x58\xa8\x31\x99\xe1\x8e\x77\ -\x0f\x71\xf7\x43\x6b\x28\xab\x54\x37\xd0\x59\x2d\xc4\x09\xb3\xb3\ -\x75\xb6\x57\x08\xea\xda\x04\x71\xf1\x89\x1c\xfb\x30\x78\x8b\x2c\ -\x75\xd8\x24\x01\x70\x38\xe8\x75\x82\xd6\xd9\xb7\x85\xcb\x1e\xd2\ -\x92\xe2\x09\x0b\x75\xd3\xd9\x35\xc0\x45\xeb\x11\xee\xdd\xfd\x47\ -\xd8\xf5\xfe\x87\x4c\x9d\x94\xfb\xf1\xcf\xba\xbd\xb0\xbd\xd2\xfe\ -\x83\xff\x69\xbd\x3a\x6c\x2d\x17\xc4\x86\x09\xe2\xc3\x25\x0e\xad\ -\xef\x67\x1d\x3d\xd0\xe9\x15\x74\xf6\xf6\xa5\xa0\x2e\x07\xc4\x84\ -\x49\x32\xa2\x21\x31\x72\x60\x9f\xc9\x63\x8d\x82\xbd\x27\x04\xde\ -\x7e\x66\xef\x1b\x10\x94\x37\xc3\xfe\x3a\x41\x51\x9a\xc4\xe5\x6b\ -\xe5\xbe\xbf\xbf\xc8\x8b\x1b\xb6\xa1\x0f\x76\x4d\x43\xf1\x8b\x8b\ -\x8a\x27\xf0\xed\x1b\x57\x18\x56\xf4\xab\xa6\xae\x81\x7b\xff\xfa\ -\x2c\x6f\xa8\x65\x44\x5b\x71\x6a\x70\x51\xa6\x64\x77\x35\x78\xa2\ -\x83\xfb\xe6\xd7\x8e\xf6\x56\x5b\x54\x03\xd4\xa0\xc7\x76\x4b\x00\ -\x9a\x26\xc8\xcb\xc9\x50\xe7\x72\xcf\x70\xdf\xa3\x2f\xf2\xc7\x9f\ -\x7e\x1b\x4d\xeb\xfb\xc0\x1c\x38\x29\xce\xda\x58\x13\x28\x1a\x3b\ -\xa1\xb1\xf3\xdc\x1f\xfa\x2e\x2f\xb4\x76\x0b\x2a\x9a\x20\x21\x02\ -\x0a\x53\x24\x51\x21\xfd\xbf\xd6\x27\xe1\xbd\x1a\x41\xd9\x79\xce\ -\x1c\x9f\xd6\xd6\xad\x73\xef\xe3\x9b\xd9\xba\xe9\x65\xda\x3b\x6c\ -\xf5\x71\x0f\x1a\xa9\x49\xa3\xf8\xe6\x97\x97\x33\x67\xda\x44\x43\ -\xda\xef\xee\xe9\xe5\xd1\xb5\xeb\x79\x74\xed\x06\xba\x7b\xd4\xd1\ -\x4d\x3b\xd2\x04\x14\xa7\x49\x0e\x66\xc6\xb2\xd1\xea\x60\x2c\xe4\ -\xf3\xf9\xe8\xee\xee\x22\x24\xd4\xda\xc2\x29\x4e\x21\xbb\x9d\x20\ -\xed\x31\x1f\x71\x86\x09\x63\x55\x02\x70\xa6\x7d\x87\xcb\x78\xf6\ -\xb5\x2d\xac\xb8\x7c\x16\x3d\x3e\x28\x1f\x40\xdd\xed\x40\x77\xb2\ -\x5d\xb0\xf1\xb0\x20\x67\x54\x5f\x91\x21\xf7\x19\x77\x56\x36\x75\ -\xc2\xae\x2a\x8d\xd6\x01\x54\xe4\xad\x38\x7e\x98\xf5\x2f\x3d\x4d\ -\x7d\xad\xba\xb4\xc7\x0a\x6e\x97\x8b\x1b\x56\x2c\xe0\xfa\xe5\x0b\ -\x09\x71\x1b\x73\x31\xd3\x9b\xdb\xde\xe7\xb7\x7f\x59\x4b\x4d\x5d\ -\x70\x5f\x24\x16\x08\x04\x50\x32\x36\xb8\x67\x00\x00\x3a\x3b\xda\ -\x2d\x4f\x00\x1c\x1a\x9d\x4e\x04\x4d\x36\x5a\x01\x00\x20\x3f\xd7\ -\x98\x5a\xdf\x81\xec\x0f\x8f\xbe\xc0\x9c\xe9\x13\x69\xf0\xc5\x7c\ -\xea\x6c\xad\xbf\x64\xc4\x48\x0a\x92\x24\x5e\x1d\xb6\x95\x0f\x6c\ -\x80\x35\x92\x0e\x1c\x39\x25\x28\x6b\x12\x24\x46\x48\x5c\x0e\x68\ -\xed\x12\x34\x0e\x60\xd1\xaa\xad\xb5\x99\x37\x5f\x7b\x9e\x7d\xef\ -\xed\xa4\xdf\x8a\x24\x8a\xe1\x66\x4e\xcd\xe7\xf6\x9b\x56\x92\x9a\ -\x64\x4c\x85\xcf\xf2\xea\x3a\xee\x79\x78\x2d\x5b\x4b\xf7\x1b\xd2\ -\xbe\x62\x8c\xe4\x04\x95\x00\x74\x77\x5b\x7f\xfa\x5e\x13\xa2\xc5\ -\x89\x4e\x93\x8d\xf6\x00\x02\x7d\x97\x7e\x28\x67\xeb\xe8\xe8\xe2\ -\x37\x0f\xad\x65\xe6\x15\x37\x1a\xd2\x7e\xa8\x13\x8a\x52\x25\x1f\ -\xad\x32\x50\x90\x24\xd9\x5a\x6e\x8f\x0f\x86\xd7\x07\xd5\x2d\x03\ -\x8b\xc5\xe7\xf3\xb2\x73\xcb\x46\xb6\xbe\xb9\x8e\xde\x1e\xb5\x8f\ -\xc4\x0a\xe9\xc9\xf1\x7c\xeb\xa6\x15\xcc\x2c\xce\x37\xa4\xfd\x8e\ -\x8e\x2e\x1e\x7c\xf2\x55\x9e\x7a\xe9\x2d\xbc\xbe\x00\xd9\x08\xa3\ -\x7c\xcc\x13\x15\x41\x78\x68\x08\x1d\x5d\xc1\x7b\xa9\x56\x4f\xb7\ -\xf5\xa7\x52\x84\x90\x2d\x4e\x29\xb0\xdd\x84\x72\x4a\x62\x1c\x71\ -\x31\x51\xea\x78\xd6\x27\x6c\xdc\xb2\x07\x57\xfc\x3b\x14\x4e\xbd\ -\xd8\xef\x6d\x87\xb9\xf8\x78\xf0\x07\x88\x0e\x95\xd8\xe9\x74\xc8\ -\x40\x94\x1d\x39\xc8\x86\x57\xd6\x70\xaa\xee\x84\xd5\xa1\x04\xa5\ -\x10\xb7\x8b\xeb\x97\x2f\xe0\x86\x15\x0b\xfd\x56\xc1\xf2\x4c\x52\ -\x4a\x36\x6e\x7d\x4f\x55\x6a\x1c\x01\x62\xa2\x23\x83\x3a\x01\xe8\ -\xea\xb2\x7e\x06\xc0\x21\x44\x93\x2d\x97\x00\x00\x26\xe6\x65\xf1\ -\xe6\xf6\x0f\xac\x0e\xc3\x76\x36\xbc\xf4\x34\xc9\x69\x99\x24\x26\ -\xfb\xb7\xa4\x66\x6b\x0f\x78\xf5\xbe\xdd\xba\xc0\x39\xef\x09\xb0\ -\xa3\xa6\x86\x7a\x36\xbe\xbc\x86\x23\x87\xd4\xa5\x3d\x56\x99\x77\ -\x51\x21\xdf\xfc\xd2\x55\x24\x27\xc4\x19\xd2\xfe\xa1\xa3\x95\xdc\ -\xf5\xc0\x1a\xde\x3f\x78\xcc\x90\xf6\x15\x73\x45\x45\x86\x43\x6d\ -\xf0\x5e\x0a\xd4\xd3\x65\xfd\x0c\x00\x9a\xde\xe0\x44\x97\x4d\x56\ -\x1f\x47\xe8\xcf\x94\xfc\xb1\x2a\x01\xe8\x87\xd7\xdb\xcb\xf3\x8f\ -\x3f\xc8\x75\xab\xef\x24\x2c\x22\xc2\x7f\xed\x7e\x54\xc2\xb7\x30\ -\x45\x22\x80\xc3\xa7\xec\xf7\x99\xf8\xa4\xde\x9e\x1e\xb6\x6d\x7e\ -\x8d\x1d\x9b\x37\xe0\xf3\x05\xe0\xb1\x88\x11\x20\x33\x35\x91\xdb\ -\x6f\x5e\xc9\xf4\xc9\xe3\x0c\x69\xbf\xb9\xa5\x9d\xfb\xfe\xf1\x22\ -\x2f\xac\xdf\xaa\x8e\x6e\x8e\x20\x9e\xc8\x11\x76\x75\xe8\x20\x75\ -\xdb\x60\x09\x40\x83\x7a\xa7\x26\x64\xa3\xb4\xe1\x54\xef\x94\x82\ -\x31\x56\x87\x60\x5b\x8d\x0d\xf5\x3c\xfd\xe8\x1f\xf9\xdc\x97\x6f\ -\xc3\xe5\x3e\xc7\x39\xb9\x21\x38\xde\x28\xa8\x6e\xe9\x2b\xa2\xd3\ -\x65\xe3\xf1\x54\x4a\xc9\x81\xf7\x77\xf1\xe6\xeb\xcf\xd3\xda\xac\ -\xa6\x82\xad\x10\x16\xea\xe6\xcb\x57\x5f\xc6\x35\xcb\x2e\xc1\xe5\ -\x74\x5c\xf8\x0d\x83\xa4\xeb\x3a\x6b\x5f\x7b\x87\xfb\xff\xf1\x12\ -\x2d\x6d\xea\xe8\xe6\x48\xe3\x89\x0c\xb7\x3a\x04\x4b\x75\xdb\x62\ -\x09\x40\xab\x75\x22\x9d\x27\x11\xf6\xab\x81\x3e\x36\x2b\x95\xc8\ -\xf0\x30\xda\x54\x09\xcf\x7e\x9d\xa8\x2a\x67\xcd\xa3\x7f\x66\xd5\ -\x17\x6e\xc5\xe9\xf4\xdf\x7a\xab\xdd\xa7\xfe\xcb\x8e\x1c\xe4\x8d\ -\xd7\x9e\xa3\xae\xa6\xd2\xea\x50\x82\xd6\xcc\xa9\xf9\xdc\xb9\x7a\ -\x95\x61\xd3\xfd\xfb\x0e\x97\x71\xf7\xfd\xcf\xb0\xef\xb0\xaa\xd4\ -\x38\x52\x45\x45\x04\x77\x02\xd0\xdb\x6b\xfd\x06\x65\xcd\xc1\x87\ -\x4e\x9f\x97\x5a\xcd\x98\xe3\xb9\xc3\xa2\x69\x1a\x85\x13\x72\xd8\ -\xb2\x2b\x78\x4b\x46\x5e\x48\xc5\xf1\x0f\x59\xf3\xb7\x3f\xb1\xfc\ -\xf3\x37\xe1\xb6\xf8\x4c\xa9\xd1\x4e\x54\x95\xf3\xe6\x6b\xcf\x53\ -\x7e\xec\x90\xd5\xa1\x04\xad\xcc\xd4\x44\x6e\xbf\x69\x25\xd3\xa7\ -\x18\x33\xdd\x7f\xaa\xa9\x85\xdf\x3f\xf2\x3c\xeb\xd4\xa5\x3d\x23\ -\x9e\x27\x2a\xb8\x13\x00\x9f\xd7\xfa\x29\x56\xb7\x8b\x03\xce\xd6\ -\x2c\x4e\x46\x57\xa3\x83\xfd\xae\x6f\x2f\x9e\x98\xab\x12\x80\x0b\ -\x28\x3f\x76\x88\x7f\x3c\xf8\x5b\x56\xdd\x70\x2b\x91\x9e\x68\xab\ -\xc3\xf1\xbb\xea\xca\xe3\xec\xd8\xbc\x9e\x0f\x0f\xbc\xaf\xce\xf3\ -\x5b\x24\x2c\x2c\x84\x9b\xaf\x59\xcc\xd5\x4b\xe7\xe0\x74\xf8\x7f\ -\xba\xbf\xd7\xeb\xe3\x89\x17\x36\xf1\x97\xa7\x5e\x0b\xea\x9d\xe1\ -\xc1\x24\x2c\xc4\x7f\x4b\x97\x81\xc8\xdb\x6b\x6d\xb5\x4a\x87\x00\ -\xf2\x39\xe6\x9c\x2f\x84\xb7\xb4\xca\xd7\x00\xc4\x5b\x1a\x51\x3f\ -\x2e\x9a\x32\x9e\x7b\xff\xf2\xac\xd5\x61\xd8\xde\xc9\xda\x2a\x1e\ -\xbd\xff\xd7\x5c\xb1\xea\x4b\xa4\x67\x8d\xb6\x3a\x9c\x61\x93\x52\ -\x72\xe4\xe0\x5e\x76\x6e\x59\x4f\x65\xd9\x51\xab\xc3\x09\x5a\x42\ -\x08\x2e\x9d\x53\xcc\xd7\xbf\xf0\x19\xe2\x63\x8d\x49\x2e\xb7\x96\ -\xee\xe7\x9e\x87\xd7\x52\x5e\x5d\x67\x48\xfb\x8a\x3d\xb9\x5c\x4e\ -\xab\x43\xb0\x94\xd7\x6b\x6d\x02\x10\xe2\xc4\x77\x8d\x10\xbe\xd3\ -\xff\x0a\x75\xd8\x30\x01\xc8\xce\x48\x26\x39\x21\x8e\x13\x27\x55\ -\x89\xcf\x0b\x69\x6d\x6e\xe2\xf1\x87\x7e\xcb\x45\xf3\x2e\xe3\xe2\ -\x79\x97\xa3\x69\xb6\x9b\xd0\xb9\x20\xaf\xb7\x97\xfd\xef\xee\x64\ -\xc7\x96\x8d\x34\xd4\xab\x5b\xdc\xac\x34\x3a\x33\x95\x3b\x57\xaf\ -\x64\x4a\xbe\x31\x9b\x71\xab\x6a\xeb\xb9\xe7\xa1\xb5\xbc\xbd\x53\ -\x1d\xdd\x0c\x46\x0e\x47\xe0\x7d\x3f\xf9\x93\xd5\x09\x80\xdb\x21\ -\xbb\x01\x9c\x00\x52\x88\x5a\x21\xa5\x31\x65\xbb\x86\xe9\xa2\xa2\ -\xf1\xac\x5d\xb7\xc5\xea\x30\x02\x82\x94\x3a\xef\x6c\x7a\x85\x63\ -\x87\xf6\xb1\x60\xe9\x2a\x52\x33\xb2\xad\x0e\x69\x40\xaa\x2a\x8e\ -\xb1\x6f\xcf\x0e\x0e\x7c\x50\x4a\xb7\x1d\xce\xc7\x06\xb1\x88\xf0\ -\x30\x6e\xfe\xdc\x62\x56\x2d\x9e\x6d\xc8\x97\x74\x57\x77\x0f\x7f\ -\x5d\xf3\x3a\x8f\x3d\xb7\x89\x1e\x8b\xa7\x41\x15\xeb\xb8\x9c\xc1\ -\x3d\x03\xe0\xb3\x38\x01\x70\x39\x44\x1b\x7c\x94\x00\x08\x29\x6b\ -\x2c\x8d\xe6\x3c\x2e\x2a\xca\x57\x09\xc0\x20\x9d\xa8\x2e\xe7\xef\ -\x0f\xdc\xcd\xf8\x49\xc5\xcc\x59\xb8\x8c\xe8\x18\x63\x76\x6b\x0f\ -\x47\x4b\x73\x23\xfb\xf6\xec\x60\xdf\x7b\x3b\x68\xa8\x57\xd3\xbf\ -\x56\x13\x42\xb0\x78\x5e\x09\x5f\xbb\xe1\x33\xc4\xc5\x44\x19\xd2\ -\xc7\x86\x2d\x7b\xb8\xf7\xaf\xcf\xaa\x2a\x7e\x8a\x21\x7b\x49\x02\ -\x89\xb7\xd7\xda\x4d\x80\x0e\x4d\x36\xc1\x47\x09\x00\x50\x61\x61\ -\x2c\xe7\x55\x3c\x69\x2c\x6e\x97\x4b\x3d\x2d\x0c\x96\x94\x1c\x78\ -\x6f\x17\x87\x3e\xd8\x4d\x5e\xfe\x14\x4a\x66\xcd\x27\x39\xd5\xc2\ -\x3b\x16\xa4\xa4\xee\x44\x15\xc7\x0e\xef\xe7\xd8\x87\xfb\xa8\x2a\ -\x3f\xa6\x76\x7a\xdb\x44\x5e\x4e\x3a\x77\xac\x5e\xc9\xa4\x71\x39\ -\x86\xb4\x7f\xb4\xbc\x86\xbb\x1f\x58\x43\xe9\xde\xc3\x86\xb4\xaf\ -\x04\x1e\x23\x6a\x47\x04\x12\x9f\xc5\x77\x58\x38\x34\x4e\xc0\xe9\ -\x25\x00\xa8\xb0\x5f\x29\xa0\x3e\xe1\xa1\x21\xcc\x98\x32\x8e\xb7\ -\x76\xa8\xaa\x80\x43\xa1\xeb\x3a\x07\x3e\x28\xe5\xc0\x07\xa5\x24\ -\xa7\x66\x32\x7e\x52\x31\xe3\x26\x16\x11\xe5\x89\x31\xbc\xef\xee\ -\xae\x0e\x8e\x1f\x3e\xd8\x37\xe8\x1f\xde\x4f\x7b\x6b\x8b\xe1\x7d\ -\x2a\x03\x17\x15\x11\xce\x2d\xd7\x2e\x61\xf9\x65\x33\x0d\xd9\x33\ -\xd2\xd6\xd1\xc9\x83\x8f\xbf\xc2\xd3\xaf\x6c\xc6\x67\xd4\x15\x96\ -\x4a\x40\x72\x06\xf9\x12\x00\xd2\xda\xdf\x07\x0d\x59\x01\xa7\x97\ -\x00\x90\x15\x76\xbe\xf8\x65\xfe\xc5\x53\x54\x02\xe0\x07\x27\xaa\ -\xcb\x39\x51\x5d\xce\x1b\xeb\x9e\x25\x21\x39\x8d\x8c\xec\xb1\x64\ -\x64\x8f\x25\x25\x3d\x8b\x88\x48\xcf\xb0\xda\xee\x6c\x6f\xa7\xbe\ -\xfe\x04\xf5\xb5\xd5\x9c\xa8\x2a\xa7\xa6\xaa\x8c\xc6\xfa\x3a\xf5\ -\x94\x6f\x43\x9a\x26\x58\x3a\x7f\x3a\x5f\xbd\x7e\x19\x31\x9e\x48\ -\xbf\xb7\xaf\xeb\x92\x97\x37\x6d\xe7\x8f\x8f\xbe\x48\x63\xb3\xba\ -\xd0\x4b\xf9\x34\x5d\x0f\xee\x84\x50\x5a\x7c\x01\x8f\xdb\xc1\x41\ -\xf8\x28\x01\xd0\x70\x54\xe8\xd8\xf7\x1f\x64\x56\x49\x3e\x6e\xa7\ -\x93\x1e\x1b\x14\x4f\x18\x09\xa4\x94\xd4\xd5\x54\x52\x57\x53\xc9\ -\xae\x77\x36\x01\x10\x12\x1a\xc6\xa8\x84\x24\x62\xe2\x12\x08\x8f\ -\x88\x24\x22\x32\x8a\x90\xd0\x30\x34\xcd\xf1\x71\xb9\xe1\x9e\xee\ -\x2e\x7a\x7b\xbb\xe9\xed\xee\xa1\xbd\xad\x95\xd6\x96\x46\xda\x5b\ -\x5b\x68\x6c\x3c\x49\x67\x7b\xbb\x85\xff\x8f\x94\x81\x1a\x37\x26\ -\x83\xef\xac\x5e\x45\x7e\x6e\x96\x21\xed\xab\x2a\x7e\xca\x40\xf4\ -\x06\xf9\x77\xb9\xb4\xf8\x5e\x0b\x87\x53\xdb\x0d\x1f\x25\x00\xdd\ -\x92\x72\x97\x7d\x27\x00\x88\x08\x0f\xa3\xa4\x30\x8f\x2d\xa5\xaa\ -\x28\x90\x51\xba\xbb\x3a\xa9\xae\x38\x4e\x75\xc5\x71\xab\x43\x51\ -\x0c\x10\x15\x11\xce\x8d\xd7\x5c\xce\xd5\x4b\x66\x1b\x32\xdd\xdf\ -\xd2\xd6\xc1\x43\x4f\xbc\xca\xd3\xaf\x6c\x0e\xfa\xa7\x3b\xe5\xc2\ -\x7a\x2d\xde\x04\x67\x35\x2b\x67\x00\x84\x80\x70\xc1\x76\xf8\x28\ -\x01\x98\x91\x2e\x4e\x95\x56\xf9\x3a\x00\xdb\xd6\x67\x5c\x38\xab\ -\x48\x25\x00\x8a\x32\x48\x9a\x26\x58\xb6\xf0\x22\xbe\x7a\xdd\x15\ -\x78\xa2\xfc\x77\x7b\xe4\x69\x3e\x9f\xce\xd3\xaf\x6c\xe6\xc1\xc7\ -\x5f\x51\xf7\x76\x28\x03\xe6\x0d\xf2\x3d\x21\x56\xce\x00\x84\xb8\ -\xd0\x57\x4e\x16\x75\xf0\xcf\x53\x00\x48\xc9\x51\x21\x98\x68\x59\ -\x54\x17\x30\x77\x46\x21\x61\xf7\x3f\x45\x67\xa7\x2a\x15\xaa\x28\ -\x03\x91\x37\x3a\x9d\xef\xdc\xb2\x8a\x82\xdc\x6c\x43\xda\xdf\xbd\ -\xf7\x30\x77\x3d\xf0\x0c\x47\xcb\xab\x0d\x69\x5f\x19\xb9\x82\x7e\ -\x09\xc0\xc2\xbd\x51\x61\x0e\x3e\xbe\x5e\xf3\x9f\x5b\x31\x85\x38\ -\x0c\xd2\xb6\x09\x40\x58\xa8\x9b\xf9\x17\x4d\xe6\xa5\x8d\xdb\xad\ -\x0e\x45\x51\x6c\x2d\xc6\x13\xc9\xad\xd7\x5d\xc1\x15\x0b\x66\xa0\ -\x69\xfe\x5f\xdb\xab\xab\x6f\xe2\xde\xbf\x3e\xcb\x86\x2d\x7b\xfc\ -\xde\xb6\x12\x1c\xbc\x5e\x9b\x5f\x3b\x6a\x30\x2b\x97\x00\x5c\x9a\ -\x3c\x75\xfa\xcf\x67\x9c\xc5\x90\xb6\x3f\xa4\xbb\xe4\x92\x69\x2a\ -\x01\x50\x94\x73\xd0\x34\x8d\xe5\x97\x5e\xcc\x2d\xd7\x2d\x35\xe4\ -\xba\xd5\x9e\xde\x5e\x1e\x7b\x6e\x13\x8f\x3c\xf3\x3a\x9d\x5d\xd6\ -\x5f\x67\xaa\x04\xae\x9e\x20\xdf\x03\x60\x65\xa9\x76\x97\xe3\x9f\ -\x75\x7f\x3e\x4e\x00\x84\xe4\x88\x8d\x4f\x02\x02\x30\x25\x7f\x0c\ -\xa9\x49\xa3\xa8\xae\x3d\x75\xe1\x17\x2b\x4a\x10\x99\x34\x2e\x87\ -\x3b\x56\xaf\x24\x2f\x27\xdd\x90\xf6\xdf\xde\xb9\x97\x7b\x1e\x5a\ -\x4b\x55\x6d\xbd\x21\xed\x2b\xc1\x25\xd8\xf7\x8b\x38\x2c\xac\x84\ -\xe8\x72\xc8\x8f\xef\x54\xff\x67\x02\x20\xf4\x23\xd2\x7e\x37\x02\ -\x9f\x45\x08\xc1\x95\x8b\x2e\xe6\x8f\x8f\xbe\x60\x75\x28\x8a\x62\ -\x0b\x9e\xa8\x08\xbe\x7c\xf5\x65\x5c\xbd\x64\x8e\x21\xd3\xfd\x95\ -\x27\xea\xb9\xe7\xc1\x67\xd4\x06\x5c\xc5\xaf\x9a\x5b\x83\xfb\xd8\ -\xb0\x10\xd6\x25\x00\x4e\xc9\xbb\x1f\xff\xf9\xf4\x1f\x34\x87\xf3\ -\x70\x20\x54\xeb\x5a\xb6\x70\x06\x0f\x3e\xf1\xaa\x2a\x0d\xac\x04\ -\x35\x87\x43\x63\xe5\xe5\xb3\xb9\xf9\xf3\x8b\x89\x0c\x0f\xf3\x7b\ -\xfb\x5d\xdd\x3d\x3c\xf4\xe4\x3a\x9e\x78\x61\x13\xbd\x41\xbe\x5e\ -\xab\xf8\x5f\x6b\x5b\xc7\x85\x5f\x34\x82\x99\x3d\x03\x20\x04\xc4\ -\x86\x41\x72\x94\x24\x42\x68\xeb\x4e\xff\xfc\xe3\x04\xa0\x30\x89\ -\xb2\xdd\xd5\x74\x02\xfe\xff\x36\xf1\xa3\x18\x4f\x24\x0b\x66\x4e\ -\xe6\x95\x37\x76\x5a\x1d\x8a\xa2\x58\x62\xf2\x84\x31\xdc\xb9\x7a\ -\x25\x63\xb2\x52\x0d\xeb\x63\xff\xe1\x72\x1e\x5d\xbb\xde\xb0\xf6\ -\x95\xe0\xd6\xdc\x12\xdc\x09\x80\x66\x42\x02\x20\x04\x24\x44\x48\ -\xd2\x3c\x90\xe2\x91\xb8\xfb\xba\x6c\x2b\x4a\x15\x07\x4f\xbf\xe6\ -\x8c\x25\x00\xa1\xef\xaa\xf2\x1d\x10\x50\x89\xce\xf1\xc3\x00\x00\ -\x20\x00\x49\x44\x41\x54\x64\x78\x64\xc3\xb4\x72\xf1\x6c\x95\x00\ -\x28\x41\x27\x32\xd2\xc3\x67\xae\xba\x92\x2b\x2e\x99\x4a\x46\xb4\ -\xd5\xd1\x28\xca\xd0\xb5\xb6\x07\x77\x02\xe0\x30\x70\x13\x60\x84\ -\x0b\xb2\xe3\x24\x19\x31\x92\xd0\x4f\x5e\xb9\x20\xe4\x3e\x21\xc4\ -\xc7\x47\x10\x3e\xf9\xd7\xfb\x08\x80\x04\x20\x3f\x37\x8b\x49\xe3\ -\x72\x78\xff\xe0\x31\xab\x43\x51\x14\xc3\x69\x9a\x46\xf1\x8c\xb9\ -\xcc\x9c\xbf\x04\x77\x48\x28\x7b\xaa\x61\xef\x09\x18\x3d\x4a\x32\ -\x36\x5e\xe2\xb2\xf7\xd6\x1d\x45\xf9\x94\x96\x20\x5f\x02\x70\x7e\ -\x54\x5e\xdd\x5f\x34\x20\xd9\x23\xc9\x8e\xed\x7b\xea\x17\xe7\xd8\ -\x0e\x24\xa5\x38\x6b\x33\xcf\x59\x09\x80\x26\xd8\x17\x28\x77\xb7\ -\x5c\xbf\x7c\x21\xdf\xfb\xc5\xfd\x56\x87\xa1\x28\x86\xca\xc8\x19\ -\xcb\xc2\xa5\x57\x13\x9f\x98\x72\xd6\xcf\x7b\x75\x38\x78\x52\x70\ -\xac\x41\x30\x21\x49\x92\x1d\x2b\xed\x7e\x88\x47\x51\x00\xf0\xfa\ -\x7c\x41\x9f\x00\xb8\x5c\x6e\xbf\xb4\xe3\x74\x40\x4e\x8c\x64\x4c\ -\x7c\x3f\x4f\xfb\xfd\xd0\xfa\x1e\xf2\xff\xf9\xfe\xb3\xff\x5a\xee\ -\xb3\xf3\xad\x80\x67\x9a\x55\x92\xcf\xe8\xcc\x14\x8e\x96\xd7\x58\ -\x1d\x8a\xa2\xf8\x5d\x44\xa4\x87\x79\x97\x5e\x49\xfe\xe4\x12\xce\ -\x99\xce\x03\x3d\x3e\x78\xb7\x5a\x50\xd1\x24\x28\x4a\xd5\x89\xf2\ -\xef\x83\x85\xa2\xf8\x5d\x5d\x7d\x53\xd0\xdf\x17\xe1\x0e\x19\xde\ -\x2f\xaa\xd3\x01\x39\xb1\x92\xdc\xf8\x8f\xd7\xf6\x07\x46\xca\xb3\ -\x12\x80\xb3\x26\x0f\x85\xee\x08\x98\xb3\x3e\x42\x08\xae\xbb\x6a\ -\x81\xd5\x61\x28\x8a\x5f\x69\x0e\x07\xc5\x33\xe6\x71\xf3\xb7\x7e\ -\x40\xfe\x94\x69\xe7\x1d\xfc\xcf\xd4\xd0\x01\x9b\x8e\x6a\x1c\x39\ -\x25\x2c\xbe\x68\x54\x51\xce\xef\x44\x7d\xa3\xd5\x21\x58\x2e\x35\ -\xd6\x4d\x8a\x47\x12\xe6\x1a\xdc\xfb\xc2\x9c\x50\x90\x2c\x59\x9c\ -\xa7\x53\x90\x34\xc8\xc1\x1f\xd0\x9c\x8e\x73\xcf\x00\x1c\x4a\xe3\ -\xc8\xd8\x6a\x6c\x7d\x29\xd0\x99\x16\xcd\x2e\xe2\xe1\x27\x5f\xa5\ -\xf2\x84\x2a\x4e\xa2\x04\xbe\xcc\xd1\x79\x2c\x5a\x7a\x35\x71\x09\ -\x49\x43\x7a\xbf\x4f\x87\xf7\x4f\x08\xea\xdb\xa1\x28\x6d\xf0\x5f\ -\x0e\x8a\x62\x86\x13\x75\x0d\x56\x87\x60\xb9\xf4\x38\x37\x33\x32\ -\x24\x20\x69\xe9\x82\x13\x6d\x82\xba\x56\x68\xec\x14\xf8\x3e\x91\ -\xc1\x6b\x40\x42\xa4\x24\x3b\x0e\x92\x23\xcf\xbd\xbe\x3f\x00\x6d\ -\x85\x49\x9c\x75\x4f\xf7\x59\x09\xc0\x35\x42\xf8\x76\x57\x7b\xdf\ -\x97\x52\xcc\x18\x72\x17\x26\x72\x3a\x1c\xdc\xfc\xb9\x25\xfc\xe7\ -\x6f\x1e\xb1\x3a\x14\x45\x19\xb2\xa8\xe8\x18\x2e\xb9\x7c\x05\xe3\ -\x0a\xa6\xf8\xa5\xbd\x9a\x56\x41\xcb\x51\xc1\xb4\x74\x9d\x18\x5b\ -\x1f\xea\x55\x82\xd1\x89\x93\x6a\x06\x20\x2c\xf4\x9f\x4b\x00\x9e\ -\x50\xf0\x84\x4a\xf2\xe2\xc1\xa7\x4b\x9a\xbb\xa0\xb5\x47\x80\x84\ -\x70\x37\xc4\x86\x49\x9c\x7e\xd8\xe8\x2b\x90\x7b\x84\x10\x67\xad\ -\xbd\x7c\xaa\x59\x89\xd8\x3d\xfc\xae\xcc\xb3\x70\x56\x11\x63\xb3\ -\xd2\xac\x0e\x43\x51\x06\xed\xf4\x74\xff\x4d\xb7\x7d\xdf\x6f\x83\ -\xff\x69\xed\x3d\xf0\xd6\x31\x8d\xa3\x0d\x81\xb1\xa7\x47\x09\x1e\ -\x27\x4e\xaa\x19\x80\xe8\x73\x5c\xcd\xed\xd0\x20\x2e\x1c\xb2\x62\ -\x24\x59\xb1\x92\x84\x08\xff\x0c\xfe\xd0\xff\xd8\xde\x4f\x02\x40\ -\x40\x25\x00\x9a\x26\x58\xfd\xf9\x25\x56\x87\xa1\x28\x83\x92\x3d\ -\x76\x3c\x5f\xfe\xfa\xf7\x58\xb0\x74\x25\x2e\x3f\x1f\x09\x3a\xcd\ -\x27\xe1\xbd\x1a\xc1\xae\x2a\x81\x37\xb8\xf7\x5c\x29\x36\x52\xa3\ -\x96\x00\xce\x99\x00\x18\x4b\x7e\xea\xfa\xce\x4f\x1d\x1c\x10\x68\ -\xa5\x10\x58\xdf\x16\xb3\xa7\x15\x50\x54\x30\x86\xdd\x7b\x8f\x58\ -\x1d\x8a\xa2\x9c\x57\xa4\x27\x9a\xb9\x0b\x3f\xd3\xb7\xc1\xcf\x24\ -\x15\x4d\x82\xa6\xce\xbe\x25\x01\x4f\xa8\x69\xdd\x2a\x4a\xbf\x8e\ -\xa8\x93\x5b\x44\x7b\xcc\xdf\x66\xa7\xe1\xb8\xf0\x0c\x40\x54\x3b\ -\xef\x23\x08\xb8\x42\xfb\xb7\xdf\xb4\xd2\xd2\x2b\x16\x15\xe5\x7c\ -\x9c\x4e\x17\x17\xcd\xbd\x9c\x9b\xbf\xf5\x43\x53\x07\xff\xa6\xc6\ -\x53\xb4\x34\x37\xd2\xda\xdd\xb7\x24\xd0\x14\xdc\x97\xb0\x29\x16\ -\xab\x6f\x6c\xa6\x25\xc8\x2f\x02\x02\xf0\x44\x9a\x3e\x03\xd0\x13\ -\xd1\xc1\xa7\x4e\xf9\x7d\x6a\xc4\xcc\xcd\x15\xdd\x48\xf6\x9b\x13\ -\x93\xff\x8c\xc9\x4a\x65\xf9\x65\x33\xad\x0e\x43\x51\x3e\x25\x6b\ -\xcc\x38\xbe\xf8\xb5\xef\x32\x7b\xe1\x52\xbf\x15\x00\xb9\x10\x6f\ -\x6f\x2f\x5b\x36\xbd\xcc\xc3\xf7\xfe\x8c\xe6\x86\xbe\xeb\xb3\x7b\ -\x75\xd8\x52\xa6\xd1\xd1\x63\x4a\x08\x8a\xf2\x29\x47\xcb\x4f\x58\ -\x1d\x82\x2d\x58\xb0\x04\xb0\x2f\x37\x57\x74\x7f\xf2\x87\xfd\xd6\ -\x0e\x92\xb0\x4d\x40\xa1\xf1\x31\xf9\xd7\xea\xcf\x2f\xe1\xf5\xb7\ -\x77\xab\x0c\x53\xb1\x85\xd8\xb8\x78\xe6\x2f\x59\xc9\xe8\xbc\x02\ -\x53\xfb\x3d\x7c\xe0\x7d\x36\xbe\xf2\x0c\xcd\x8d\xa7\x3e\xf5\x77\ -\x3d\x3e\x78\xef\x84\xe0\xa2\x4c\x55\x2d\x40\x31\x9f\x2a\xdc\x06\ -\xe1\xa1\x21\x84\xb8\x07\x59\x00\x60\xb8\x04\x5b\xfb\xfb\x71\xbf\ -\x09\x80\x40\x6e\x03\x71\x8b\xb1\x11\xf9\x9f\x27\x32\x9c\x6f\x7f\ -\x79\x39\x3f\xf9\xed\xa3\x56\x87\xa2\x04\x31\x97\xcb\xcd\xb4\xd9\ -\x0b\x98\x3e\x7b\x11\x4e\xa7\x79\xbf\xe8\x8d\x0d\xf5\x6c\x7c\x79\ -\x0d\x47\x0f\xed\x3d\xef\xeb\x6a\x5b\x05\x3d\x3e\x55\x27\x40\x31\ -\x9f\x4a\x00\x20\x3e\xce\xfc\x9b\xbc\x84\x2e\xb7\xf5\xf7\xf3\x7e\ -\x13\x00\x87\xc3\xb1\xcd\xe7\x0b\xac\x8d\x80\xa7\x5d\x3e\xaf\x84\ -\x75\x9b\x77\xb1\x6d\xf7\x01\xab\x43\x51\x82\xd0\xb8\xfc\x29\xcc\ -\x5b\xbc\x1c\x4f\x74\xac\x69\x7d\xf6\xf6\xf6\xb0\xf5\xcd\x75\xec\ -\x7c\x7b\x23\x3e\x9f\xf7\x82\xaf\x97\x40\x53\xa7\x20\x31\x52\xcd\ -\x02\x28\xe6\xfa\xf0\x58\x95\xd5\x21\x58\xce\x8a\x04\xc0\xa7\x39\ -\x06\x3e\x03\x50\x98\xc4\xbe\xdd\x35\x34\x23\x09\xc8\x4b\x47\xff\ -\xf5\xd6\x6b\xb8\xe1\x8e\x5f\xd0\xd9\xf9\xa9\x25\x0f\x45\x31\x44\ -\x5c\x7c\x22\xf3\x17\xaf\x24\x27\x77\x82\xa9\xfd\x1e\x39\xf8\x01\ -\x1b\x5e\x7a\x9a\xe6\xa6\xc1\x1d\xad\xea\x08\xb8\x6d\xbe\x4a\xa0\ -\xeb\xe8\xe8\x52\x27\x00\x80\x84\x51\xa6\x0f\xab\x4d\x53\x53\x38\ -\xd4\xdf\x5f\xf4\xbf\x04\x20\x84\x5e\x5a\xa5\xef\x04\xb9\xd0\xd8\ -\xb8\x8c\x91\x14\x1f\xcb\x37\xbf\x78\x15\xbf\xbc\xef\x09\xab\x43\ -\x51\x46\x38\x97\x3b\x84\x59\xf3\x97\x50\x3c\x63\x2e\x9a\xc3\xbc\ -\x39\xf5\x86\xfa\x5a\xd6\xbf\xf4\x34\x65\x47\x0e\x0e\xe9\xfd\x5d\ -\x17\x9e\x28\x50\x14\xbf\xda\xfb\x61\x79\xd0\x5f\x02\x04\x90\x60\ -\xfa\x0c\x80\xd8\xfa\xc9\x0a\x80\xa7\x9d\xf3\x02\x41\x21\xe4\x56\ -\x29\x09\xc8\x04\x00\xe0\xca\x4b\x2f\x66\xe7\xfb\x87\xd8\xb0\xe5\ -\x53\xb5\x0f\x14\xc5\x2f\xc6\xe4\x15\xb0\x68\xd9\x35\x44\x45\xc7\ -\x98\xd6\x67\x6f\x6f\x0f\x3b\xde\x5e\xcf\xb6\x37\x5f\x1f\xd0\x74\ -\xff\xb9\x74\xaa\x19\x00\xc5\x64\xef\x1d\x38\x6a\x75\x08\xb6\x90\ -\x38\xca\xbc\xef\x0b\x00\x49\xff\xeb\xff\x70\x9e\x04\x40\x0a\xf9\ -\x36\x32\xb0\xcb\x88\x7e\xf7\xd6\xcf\xb2\xf7\x50\x19\xb5\xea\xf6\ -\x29\xc5\x8f\x12\x92\xd2\x58\xb8\x6c\x15\xe9\x99\x63\x4c\xed\xf7\ -\xc8\xc1\x0f\x58\xff\xe2\x53\xb4\x34\x0f\xff\xf3\xdc\xd0\x21\x40\ -\xdd\x1b\xa8\x98\x48\x25\x00\x7d\x92\xe2\xcd\xdb\x1f\x04\x20\xd0\ -\x37\x9f\xeb\xef\xce\x99\x00\x74\x38\x1d\x9b\xc3\x7b\x74\xef\xf9\ -\x5e\x63\x77\x51\x11\xe1\xfc\xf0\x9b\xd7\x71\xfb\x7f\xfd\x81\x40\ -\xdd\xd4\xa8\xd8\x47\x48\x68\x38\xb3\x17\x2c\x65\xca\xf4\x59\x08\ -\x61\x5e\xd1\xa9\xfa\xba\x1a\xd6\xbf\xf4\x14\x15\xc7\x0e\xfb\xad\ -\xcd\xb6\x6e\x68\xee\x84\x68\x75\x59\x90\x62\x02\x5d\xd7\xd9\x77\ -\xb8\xdc\xea\x30\x6c\x21\x2d\x69\x94\x79\x9d\x09\x7a\x1d\x9a\xf3\ -\x9d\x73\xfd\xf5\x39\xbf\xc5\x66\x27\x88\xd6\xfe\x6a\x07\x07\x9a\ -\xa2\x82\xb1\x7c\xfd\x0b\x57\x5a\x1d\x86\x12\xc8\x84\x20\x7f\xf2\ -\x34\x6e\xfe\xd6\xf7\x29\x9a\x31\xc7\xb4\xc1\xbf\xb7\xa7\x87\x2d\ -\x9b\x5e\xe6\x91\x3f\xfe\xaf\x5f\x07\x7f\xe8\x7b\xf6\xdf\x5b\x27\ -\xd4\x1c\x80\x62\x8a\xbd\x1f\x96\xa9\x4d\xd9\x80\x10\x82\x14\x33\ -\x13\x00\x29\x77\x4e\x4e\x16\xe7\x2c\x8c\x73\xfe\xa7\x7b\x21\xde\ -\x44\x52\xe2\xf7\xa0\x4c\xf6\xb9\x65\xf3\x38\x52\x56\xcd\x4b\x1b\ -\xb7\x5b\x1d\x8a\x12\x60\x92\x52\xd2\x59\x78\xc5\x67\x49\xcd\xc8\ -\x36\xb5\xdf\x23\x07\x3f\xe0\xf5\x17\x9f\xa4\xb5\xb9\xc9\xb0\x3e\ -\xea\xda\x04\x47\x4e\xc1\xd8\x51\x2a\x0d\x50\x8c\xb5\xb5\x54\x1d\ -\xcb\x86\xbe\x13\x00\xe6\x16\x01\x12\x6f\x9e\xef\x6f\xcf\x9b\x00\ -\x08\x29\xdf\x94\x88\x3b\xfd\x1b\x90\x35\xee\x5c\xbd\x8a\x23\xe5\ -\x35\x1c\x3c\x52\x61\x75\x28\x4a\x00\x08\x0b\x8f\x64\xee\xa2\x65\ -\x4c\x2c\xbe\x08\x21\xcc\xdb\x0b\x73\xb2\xb6\x8a\xf5\x2f\x3e\x45\ -\x65\x99\x39\xeb\xa5\x7b\x6b\x05\x9e\x10\x54\x4d\x00\xc5\x50\x5b\ -\xf7\x04\x5c\x75\x79\x43\x98\x3a\xfd\x0f\x08\x5d\x0e\x3d\x01\x70\ -\xeb\x8e\xb7\xba\x35\x5d\xe7\x3c\x4b\x05\x81\x22\x34\xc4\xcd\xff\ -\x7e\x6f\x35\xb7\xfe\xe0\x37\xea\x3a\x4a\xe5\x9c\x84\xd0\x98\x32\ -\x6d\x16\xb3\x17\x2e\x25\x24\xd4\xbc\x1b\xbb\xba\xbb\x3a\xd9\xbc\ -\xe1\x45\xf6\x6c\x7f\x1b\x29\xcd\xdb\xaf\x22\x25\xec\xa8\x10\xcc\ -\x1d\x2d\x89\x32\xe6\x56\x62\x25\xc8\x35\x34\xb5\x72\xe8\x68\xa5\ -\xd5\x61\xd8\x42\x7a\x4a\x82\x99\xdd\xf9\xf0\x3a\xb6\x9c\xef\x05\ -\xe7\x1d\xd8\x0b\x32\x44\x83\x84\x77\xfd\x1b\x93\x75\xe2\x62\x3d\ -\xfc\xea\xfb\x5f\xc1\x13\x69\xfe\x55\x8c\x8a\xfd\x25\xa7\x65\x72\ -\xdd\x2d\xb7\xb3\xf0\x8a\xab\xcd\x1b\xfc\xa5\x64\xdf\x9e\x1d\x3c\ -\xf0\xdb\xff\x61\xf7\xb6\xb7\x4c\x1d\xfc\x4f\xeb\xd5\xe1\x9d\x72\ -\x0d\xaf\xcf\xf4\xae\x95\x20\xb0\x75\xf7\x7e\xa4\x54\x33\x4c\x00\ -\x39\xe9\xc9\xe6\x75\x26\xe4\xae\xa2\x1c\x71\xde\x35\xc4\x0b\xee\ -\xf0\xd7\xe0\x35\x09\x45\xfe\x8b\xca\x5a\x59\xe9\x49\xfc\xfc\x7b\ -\x37\xf3\x9d\xff\xbe\x8f\xce\x2e\x75\x2d\x9a\x02\x11\x91\x1e\xe6\ -\x5d\x76\x25\xf9\x85\x25\x60\xe2\x74\x7f\x6d\x75\x05\xeb\x5f\x7c\ -\x8a\xea\xca\xe3\xa6\xf5\x79\x2e\x1d\x3d\x7d\x9b\x02\x27\xa7\xa8\ -\x2f\x6a\xc5\xbf\xb6\xec\x3a\xff\xdd\x14\xc1\x24\x27\x23\xc5\xb4\ -\xbe\x04\xe2\xb5\x0b\xbd\xe6\x82\x53\xfb\x12\xfd\x82\x8d\x04\x9a\ -\xc2\xf1\xa3\xf9\xbf\x1f\xdc\x4a\x58\xa8\x39\x57\xb3\x2a\xf6\xa4\ -\x69\x1a\xc5\x33\xe6\x71\xf3\xb7\x7e\x40\xfe\xe4\x69\xa6\x0d\xfe\ -\xdd\x5d\x1d\x6c\x78\x69\x0d\x7f\xfb\xd3\xaf\x6d\x31\xf8\x9f\x76\ -\xbc\x51\xd0\xad\x2a\x04\x2a\x7e\xd4\xd1\xd1\xc5\x3b\xa5\x6a\xfd\ -\xff\xb4\xd1\x59\xe6\xcd\x00\x48\xa1\x5d\x70\xec\xbe\xe0\x0c\x40\ -\x73\x8f\x73\x73\xb4\x5b\xef\x04\x46\xd4\x89\xe1\xc2\x09\xa3\xf9\ -\x9f\x7f\xb9\x89\xef\xfd\xfc\x7e\x7a\xbc\xea\x5b\x2f\xd8\x64\x8e\ -\xce\x63\xe1\x92\x55\x8c\x4a\x34\xf1\x17\x52\x4a\xde\x2f\x7d\x87\ -\x37\x5f\x7f\x81\xae\x0e\xfb\x5d\x59\x2d\x25\x54\x36\x0b\xc6\xa8\ -\x53\x01\x8a\x9f\xbc\xb9\xe3\x03\xba\x7b\x54\xd9\x49\xe8\xab\x4b\ -\x33\x2a\xc6\x63\x56\x77\xed\x51\x6d\xfd\x5f\x01\x7c\xa6\x0b\x26\ -\x00\xf3\x73\x44\x57\x69\x95\xbe\x19\xe4\xa5\xfe\x89\xcb\x3e\xa6\ -\x4f\x19\xc7\x2f\xbf\x7f\x0b\xdf\xff\xe5\x83\x74\x74\xa9\x33\xaa\ -\xc1\x20\x2a\x3a\x86\x4b\x2e\x5f\xc1\xb8\x82\x29\xa6\xf6\x5b\x53\ -\x55\xc6\xfa\x17\x9e\xe2\x44\xb5\xbd\x8b\xa1\x34\x75\x59\x1d\x81\ -\x32\x92\xbc\xb6\x79\x97\xd5\x21\xd8\x46\x4e\xa6\x79\x0f\x1b\x02\ -\xb1\x31\x37\x57\x5c\x70\x50\x1b\x50\x95\x3f\x81\x7c\x4d\xc2\x88\ -\x4b\x00\x00\x4a\x0a\xf3\xf8\xcd\x8f\xbf\xce\xbf\xfc\xf4\x4f\x34\ -\xb7\xd8\xef\xa9\x4c\xf1\x0f\xcd\xe1\x60\x4a\xc9\x6c\xe6\x2c\xba\ -\x02\x97\xdb\xbc\xed\xee\x5d\x9d\x1d\x6c\xd9\xf8\x0a\xbb\xb7\x5b\ -\xb3\xc1\x6f\xb0\xbc\xf6\x0f\x51\x09\x10\x4d\x2d\x6d\xec\x7c\xaf\ -\xdf\x4b\xe8\x82\xd2\xf8\xd1\xe9\xa6\xf5\x25\x85\x1c\xd0\xd2\xfd\ -\x80\x12\x00\xcd\xa1\xbd\xec\xf3\xe9\xbf\x1c\x5e\x48\xf6\x35\x61\ -\x6c\x26\xbf\xfd\xf1\x37\xf8\xee\x4f\xff\xac\xee\x0d\x18\x81\x46\ -\xe7\xe6\x33\x7f\xc9\x4a\x62\x47\x99\x77\x04\x47\x4a\x9d\x3d\x3b\ -\xde\x66\xf3\xfa\x97\xe8\xee\xea\x30\xad\xdf\xe1\x0a\x31\xef\x42\ -\x43\x65\x84\x5b\xff\xf6\x1e\x55\x82\xfd\x0c\xe3\x46\x67\x98\xd6\ -\x97\x43\x6a\xaf\x0e\xe4\x75\x03\x4a\x00\x26\x27\x8b\x0f\x4a\xab\ -\x7c\xc7\x80\x9c\x61\x45\x65\x63\xa3\x33\x53\x78\xf0\x97\xdf\xe1\ -\xfb\xff\xfb\x10\xef\xee\x3f\x62\x75\x38\x8a\x1f\x44\x45\xc7\x30\ -\x67\xc1\x32\xf2\xa7\x4c\x33\xb5\xdf\x13\xd5\xe5\xac\x7f\xe1\x29\ -\x6a\xaa\xca\x4c\xed\xd7\x1f\xd4\xdd\x00\x8a\xbf\x3c\xfb\xda\x79\ -\x8f\xa0\x07\x9d\x09\x63\x4d\x4a\x00\x04\x47\x26\xa7\x8a\x01\xdd\ -\x13\x3e\x98\x8b\x7e\x5e\x04\x6e\x1b\x5a\x44\x81\x21\xda\x13\xc1\ -\xaf\xff\xe3\x56\x7e\xfe\xfb\xc7\x78\x6d\x73\xa9\xd5\xe1\x28\x43\ -\xe4\x72\xb9\x99\x3e\x67\x11\xd3\x66\x2d\xc0\xe9\x34\xaf\xec\x66\ -\x47\x5b\x2b\x6f\xbc\xf6\x1c\x7b\xdf\xdd\xd1\xb7\xa3\x2e\x00\xa9\ -\xeb\xda\x15\x7f\xd8\xb3\xef\x08\x47\xcb\x6b\xac\x0e\xc3\x36\xc2\ -\xc2\x42\x48\x4f\x49\x34\xab\xbb\x67\x07\xfa\xc2\x01\x27\x00\x52\ -\xe8\x2f\x0a\xa9\x8d\xe8\x04\x00\x20\xc4\xed\xe2\x47\xb7\x7f\x81\ -\x89\xe3\x72\xf8\xdd\x5f\x9e\x55\x27\x04\x02\x4c\x5e\xfe\x64\x2e\ -\xb9\x7c\x39\x9e\x98\x38\xd3\xfa\xd4\x75\x9d\xdd\xdb\xdf\xe2\xed\ -\x8d\x2f\xd3\xd3\xd5\x69\x5a\xbf\x46\xd8\x57\x27\xf0\x84\x5a\x1d\ -\x85\x12\xe8\x9e\x5d\xa7\x9e\xfe\xcf\x34\x2e\x27\x1d\x4d\x33\xe7\ -\x98\xb1\xd4\xf5\x17\x07\xfa\xda\x01\x27\x00\x9e\x76\xe7\xc6\x96\ -\x70\xbd\x55\x40\xd4\xd0\xc2\x0a\x2c\xab\x96\xcc\x26\x3f\x37\x93\ -\x1f\xdd\xf5\x57\xaa\x6b\x4f\x59\x1d\x8e\x72\x01\x29\xe9\x59\x5c\ -\x72\xf9\x72\xd2\x32\x47\x9b\xda\x6f\xc5\xf1\xc3\xac\x7f\xe9\x69\ -\xea\x6b\xab\x4d\xed\xd7\x28\x3e\x1d\xb6\x94\x09\xb4\x66\xf3\x0a\ -\x22\x29\x23\x4b\x63\x73\x2b\x9b\xb6\x8e\x98\x02\xb2\x7e\x51\x90\ -\x97\x6d\x56\x57\x2d\xa1\xcd\xce\xcd\x03\x7d\xf1\x80\x13\x80\xdc\ -\x5c\xd1\xbd\xbb\x4a\x7f\x5d\x22\x57\x0c\x2d\xae\xc0\x33\x61\x6c\ -\x26\x0f\xff\xea\xbb\xdc\xf3\xf0\x33\xbc\xb0\x7e\x9b\xd5\xe1\x28\ -\xfd\x48\x48\x4a\xe3\xe2\x4b\x2e\x27\x6f\x42\xa1\xa9\x55\xfc\xda\ -\x5a\x9b\xd9\xb4\xee\x59\x0e\xbc\x5f\x1a\xb0\xd3\xfd\xe7\x22\x25\ -\x1c\x6f\x50\x09\x80\x32\x34\x6b\xd7\x6d\xa1\x57\xd5\x95\x3e\xcb\ -\xe4\x09\xe6\x3c\x98\x08\x78\xa5\xa0\x40\x0c\xb8\xc4\xed\x60\xf6\ -\x00\x80\xd4\x9f\x43\x88\xa0\x49\x00\x00\xc2\xc3\x42\xf8\xde\xd7\ -\x3e\xcf\xdc\x19\x85\xfc\xe2\xf7\x8f\x73\xaa\xa9\xc5\xea\x90\x14\ -\x20\x31\x25\x9d\x8b\xe7\x5d\x4e\xee\xf8\x49\xa6\x0e\xfc\xba\xcf\ -\x47\xe9\xd6\x37\x78\x7b\xd3\x2b\xf4\xf6\xa8\xda\x11\x8a\x72\xa6\ -\x8e\xae\x6e\x9e\x7c\xe9\x2d\xab\xc3\xb0\x15\x4d\x13\x4c\x1a\x9f\ -\x6d\x4a\x5f\xba\x90\xcf\x0f\xe6\xf5\x83\x4b\x00\x7a\x1d\x6b\x71\ -\xeb\xf7\x01\x41\x57\x43\x77\x66\x71\x3e\x8f\xdd\xfb\x03\x1e\x7e\ -\x6a\x1d\xff\x78\x6e\x23\xba\xda\x2d\x65\x3e\x21\xc8\x1a\x9d\x47\ -\xf1\x8c\xb9\x8c\xc9\x2b\x30\x75\xe0\x07\xa8\x38\x76\x98\xf5\x2f\ -\x3d\x45\x7d\x9d\xda\xdc\xa4\x28\xfd\x59\xfb\xea\xdb\xb4\xb4\xaa\ -\x7a\x2a\x67\xca\xc9\x48\x21\x2a\xc2\x94\xcb\xc5\x7a\x5c\xbd\x8e\ -\x01\xaf\xff\xc3\x20\x13\x80\xa2\x1c\xd1\x54\x5a\xa5\xbf\x31\x12\ -\xab\x02\x0e\x44\x58\xa8\x9b\xaf\xdd\xb0\x8c\x84\xd1\x45\x3c\xf9\ -\xe4\xd3\x54\x55\x1c\xb3\x3a\xa4\xa0\xe0\x0e\x09\x25\x7f\x72\x09\ -\xc5\x33\xe6\x12\x17\x9f\x64\x7a\xff\xad\x2d\x4d\x6c\x7a\xe5\x19\ -\x0e\xee\xdd\x63\x7a\xdf\x8a\x12\x28\xba\x7b\x7a\x79\xec\x85\x37\ -\xac\x0e\xc3\x76\x26\x8f\x37\xe7\xf4\xbc\x14\xe2\xb5\xc2\x2c\x31\ -\xa8\x42\x36\x83\x9b\x01\x00\xa4\x94\x4f\x0b\x31\x32\xab\x02\x0e\ -\x44\x63\x27\x38\xa3\x33\xb8\xf6\xe6\x6f\x73\x68\xff\x7b\xbc\xf5\ -\xfa\xf3\x34\x9e\x3a\x69\x75\x58\x23\x52\x72\x6a\x26\x93\x4b\x66\ -\x32\x6e\x52\x31\x6e\x13\xab\xf7\x9d\xe6\xf3\xf9\xd8\xb5\x65\x23\ -\xef\xbc\xf9\x2a\xbd\x3d\xea\xe6\x48\x45\x39\x9f\x17\x37\x6c\xa3\ -\xa1\x51\x2d\x91\x7e\x52\xd1\xa4\x5c\x73\x3a\x92\xfa\xd3\x83\x7d\ -\xcb\xa0\x13\x00\xb7\x4b\x5b\xdb\xeb\xd5\x7f\x07\x04\x65\xcd\xb0\ -\x43\xf5\x1f\x4d\x3b\x0b\x41\x5e\xfe\x64\xc6\x8e\x9b\xc8\x07\x7b\ -\xb6\xb3\xed\xad\xd7\x68\x6e\x54\xa7\x05\x86\x2b\x22\xd2\xc3\xf8\ -\x49\xc5\x14\x4c\x9e\x46\x62\x8a\x79\xa5\x33\x3f\xe9\xf8\xe1\x03\ -\x6c\x78\xf9\x69\x1a\xea\xeb\x2c\x8b\x41\x51\x02\x45\x67\x57\x0f\ -\x7f\x79\x6a\xc4\x5d\x1c\x3b\x6c\x9a\xa6\x51\x62\x4e\x02\xe0\x95\ -\xc2\x31\xa8\xf5\x7f\x18\x42\x02\x30\x29\x49\xd4\x96\x56\x79\xdf\ -\x06\x31\x77\xb0\xef\x0d\x74\xad\xdd\x70\xa2\xf5\xec\x75\x67\xcd\ -\xe1\xa0\x70\xea\xc5\x4c\x9c\x32\x9d\xfd\xef\xed\x64\xdb\xdb\xeb\ -\x69\x38\x59\x6b\x51\x84\x81\xc9\x1d\x12\xca\xd8\x09\x93\xc8\x2f\ -\x2c\x21\x6b\x74\x1e\x42\x5c\xf0\x96\x6a\xc3\x34\x37\x35\xb0\xe9\ -\x95\x67\xf8\x70\xff\x7b\x96\xc5\xa0\x28\x81\xe6\x1f\xcf\x6d\x54\ -\x1b\xa4\xfb\x91\x3f\x36\xd3\xa4\xf5\x7f\xb1\xb1\x24\x55\xd4\x0f\ -\xf6\x5d\x83\x4e\x00\x00\x24\xe2\x49\x01\x41\x97\x00\x94\x35\x8a\ -\x73\x9e\xf8\xd2\x1c\x0e\x0a\x8a\x66\x50\x30\x65\x3a\xc7\x8f\x1e\ -\xa4\xf4\x9d\x37\x38\x76\x78\x3f\x72\x84\x1d\x11\xf3\x97\xb0\xf0\ -\x48\xc6\x8e\x9f\x48\xee\x84\x42\xb2\xc6\x8c\xc3\xe1\x18\xd2\x47\ -\xd1\x6f\xbc\xde\x5e\x76\xbc\xbd\x81\xed\x6f\xbd\x4e\x6f\xaf\x9a\ -\xee\x57\x94\x81\x3a\xd5\xd4\xc2\x63\xcf\x6d\xb4\x3a\x0c\x5b\x9a\ -\x3e\x65\x9c\x29\xfd\x48\x21\x9f\x1a\xca\xfb\x86\xf4\xad\x2b\x34\ -\xed\x71\x74\xfd\xae\xa1\xbe\x3f\x50\xd5\xb5\x0d\x60\xd7\xb9\x10\ -\x64\x8f\x19\x4f\xf6\x98\xf1\x34\x37\x9e\xe2\x83\x3d\xdb\xd9\xbb\ -\x7b\x1b\x2d\xcd\xea\x92\xa1\x51\x09\xc9\xe4\xe4\x4e\x60\x4c\xde\ -\x44\xd2\xb3\x47\x5b\xfa\xa4\x7f\xa6\x23\x87\xf6\xb2\xf1\xe5\x35\ -\x34\x35\x0c\x3a\x81\x56\x94\xa0\xf7\xc0\x63\x2f\xab\xeb\xd4\xcf\ -\x61\xda\x64\x53\x12\x80\x1e\xaf\xae\x0d\x7a\xfd\x1f\x86\x38\x80\ -\x17\xa7\x88\x93\xa5\x55\xfa\xeb\x20\x17\x0f\xe5\xfd\x81\x48\x02\ -\xed\x83\x7c\x30\x8c\x8e\x1d\xc5\xac\xf9\x4b\x98\x79\xc9\x62\x2a\ -\xcb\x8e\x70\xf8\xc0\xfb\x1c\x3e\xf0\x7e\xd0\xec\x15\x08\x8b\x88\ -\x20\x2b\x27\x8f\xec\xb1\x13\xc8\x1e\x33\x9e\x48\x4f\xb4\xd5\x21\ -\x9d\xa5\xa9\xa1\x9e\x8d\x2f\xaf\xe1\xc8\xa1\xbd\x56\x87\xa2\x28\ -\x01\xe9\xd0\xb1\x4a\x5e\xd8\xb0\xdd\xea\x30\x6c\x29\xc6\x13\x49\ -\x41\x6e\x96\x09\x3d\x89\x97\x67\xa4\x8b\x21\x0d\x2a\x43\x7f\x82\ -\x17\xfa\xdf\x91\x22\x68\x12\x00\x24\xe8\x43\x9c\xcd\x17\x42\x90\ -\x91\x3d\x96\x8c\xec\xb1\xcc\x5f\xbc\x82\x53\x27\x4f\x70\xe4\xe0\ -\x07\x1c\x39\xb8\xb7\xef\x28\xe1\x08\x59\x26\x88\x88\xf4\x90\x9e\ -\x35\x9a\xd4\xcc\xd1\xa4\x67\x8e\x26\x31\x25\x1d\x61\xf2\x59\xfd\ -\x81\xf0\xf6\xf6\xb2\xfd\xed\xd7\xd9\xfe\xd6\x7a\xbc\xde\x5e\xab\ -\xc3\x51\x94\x80\xa4\xeb\x92\xff\xfb\xd3\xd3\xaa\x26\xca\x39\xcc\ -\x9e\x56\x80\xa6\x19\x3f\xcb\x29\xd1\xff\x31\xd4\xf7\x0e\x39\x01\ -\x70\x68\x8e\x35\x3e\x9f\xfe\x07\x20\x62\xa8\x6d\x04\x12\x21\xc0\ -\xa9\x41\xaf\x1f\x3e\xeb\xa3\x12\x92\x19\x95\x90\xcc\xf4\xd9\x8b\ -\x68\x6b\x6d\xa6\xaa\xe2\x18\x35\xe5\xc7\xa8\xae\x3c\x4e\x6d\x75\ -\x05\x3e\x9f\xfd\xcb\x68\x46\x45\xc7\x90\x98\x9c\x4e\x62\x72\x1a\ -\x09\xc9\x69\xa4\xa6\x67\xdb\xee\x09\xbf\x3f\x1f\x1e\x78\x9f\x4d\ -\x2f\xaf\xa1\xb9\xa9\xc1\xea\x50\x14\x25\xa0\x3d\xf5\xd2\x9b\xec\ -\xfd\xf0\xb8\xd5\x61\xd8\xd6\xec\x92\x49\x66\x74\xd3\xee\x74\x38\ -\x5e\x18\xea\x9b\x87\x9c\x00\x4c\x4e\x16\xed\xa5\x55\xbe\x17\x80\ -\xcf\x0d\xb5\x8d\x40\x13\x13\x26\x39\xd9\xee\xdf\x27\xda\xc8\xa8\ -\x68\xc6\xe5\x4f\x61\x5c\xfe\x14\x00\x7c\x3e\x2f\x27\xaa\x2b\xa8\ -\xa9\x38\xce\xa9\x93\x27\x68\xac\x3f\x49\xc3\xa9\x3a\x3a\xda\x5b\ -\xfd\xda\xef\x40\x68\x0e\x07\xd1\x31\x71\xc4\xc4\x25\x10\x1b\x97\ -\x40\xec\xa8\x04\xe2\xe2\x13\x49\x4a\x49\x27\x34\x3c\xb0\xf2\xbe\ -\x86\xfa\x3a\x36\xbe\xb2\x86\x63\x1f\xee\xb7\x3a\x14\x45\x09\x78\ -\xb5\xf5\x8d\xdc\xff\xd8\xcb\x56\x87\x61\x5b\x61\xa1\x6e\xa6\x4d\ -\xce\x33\xbe\x23\xc9\x9a\xc9\xc9\x62\xc8\xa5\x17\x87\xb5\x89\x4f\ -\x48\xf9\x88\x14\x22\x68\x12\x80\xc4\x28\x38\x69\x70\x95\x4b\x87\ -\xc3\x49\x5a\x46\x0e\x69\x19\x67\x57\x8f\xea\xe9\xea\xa4\xa1\xe1\ -\x24\x8d\xa7\x4e\xd2\xda\xd4\x48\x47\x47\x1b\x9d\x1d\xed\x74\x75\ -\xb4\xd3\xd9\xd1\x4e\x67\x67\x3b\x1d\xed\x7d\xc1\xf9\xbc\xbd\xfd\ -\x4e\x6d\xbb\x5c\x6e\x1c\x4e\x27\xa1\xa1\x61\x38\x5c\x2e\x9c\x4e\ -\x17\x61\xe1\x11\x84\x47\x46\x11\x11\xe9\x21\x32\xd2\x43\x44\x44\ -\x14\x11\x9e\x68\x22\x3d\x31\x44\xc7\xc4\x99\x32\x85\x65\xa4\xde\ -\x9e\x1e\xb6\xbe\xb9\x8e\x9d\x5b\x36\xe2\xf3\xa9\xab\x9d\x15\x65\ -\xb8\xa4\x94\xfc\xf2\xbe\x27\xd4\xc6\xbf\xf3\x98\x3e\x65\x3c\x21\ -\x6e\x97\xe1\xfd\x08\xa1\x3f\x32\x9c\xf7\x0f\x2b\x01\xf8\x30\xcd\ -\xf1\xca\xd8\x6a\xbd\x12\xb0\xae\x62\x8b\x89\x72\x62\x25\x65\x8d\ -\x82\x36\x0b\x3e\xf7\xee\xd0\x30\x92\x53\x33\x49\x4e\xcd\x1c\xf4\ -\x7b\xa5\xd4\x6d\xb3\xe3\xde\x4c\x07\xf7\xee\x61\xd3\xab\xcf\xd0\ -\xda\xdc\x64\x75\x28\x8a\x32\x62\x3c\xf5\xf2\x5b\x6c\xdb\x7d\xc0\ -\xea\x30\x6c\x6d\xe1\xcc\x22\x33\xba\xa9\xfc\x30\xd5\xb9\x61\x38\ -\x0d\x0c\x6b\x54\xb8\x46\x08\x1f\xf0\xe8\x70\xda\x08\x24\x4e\x0d\ -\x66\x66\xe9\x84\x1b\x9f\xd8\xf9\x55\xb0\x0d\xfe\xed\xad\x2d\x3c\ -\xf1\x97\xdf\xf1\xfc\x13\x0f\xa9\xc1\x5f\x51\xfc\xe8\x68\x79\x0d\ -\x7f\x78\x64\xc8\x4b\xce\x41\x21\x2c\x2c\x84\x59\x25\x05\xc6\x77\ -\x24\x78\xe8\xa3\x31\x78\xc8\x86\x3d\x32\x68\x52\x7b\x90\xbe\x53\ -\x72\x41\x21\xdc\x05\xb3\xb2\x75\xdc\x41\x59\x08\x39\x30\xd4\xd7\ -\xd5\x50\x7e\xf4\x90\xd5\x61\x28\xca\x88\xd2\xe3\xf5\xf2\x5f\xbf\ -\x79\x94\x9e\x5e\x75\x72\xe6\x7c\x2e\x99\x51\x68\xc6\xf4\xbf\xd4\ -\xbd\xda\x5f\x86\xdb\xc8\xb0\x13\x80\x29\xe9\xe2\x10\xc8\x77\x86\ -\xdb\x4e\x20\x89\x70\xc3\xf4\x4c\x1d\xfb\x1d\x70\x53\x14\x45\x31\ -\xc6\x6f\x1e\x78\x86\xc3\x65\x55\x56\x87\x61\x7b\x8b\x66\x15\x1b\ -\xdf\x89\x90\x6f\x94\x64\x8a\x23\xc3\x6d\xc6\x5f\x73\xc3\x0f\xfa\ -\xa9\x9d\x80\x11\x1f\x0e\xf1\x11\x41\x33\xf1\xa1\x28\x4a\x10\x7b\ -\xee\xf5\xad\x3c\xfb\xda\x16\xab\xc3\xb0\xbd\x51\x31\x1e\x4a\x0a\ -\x4d\xd8\xfd\xaf\xf3\x90\x3f\x9a\xf1\x4b\x02\x10\xe2\x72\x3c\x0e\ -\x04\xdd\x4d\x10\x21\x41\x55\x08\x59\x51\x94\x60\xb4\xf7\xc3\xe3\ -\xdc\x7d\xff\x90\x2a\xcd\x06\x9d\xa5\x0b\xa6\xe3\x70\x18\xbe\xe7\ -\xaa\x49\xd7\x1c\x43\xaa\xfd\xff\x49\x7e\x89\xb4\x20\x51\xb4\x49\ -\xf8\x9b\x3f\xda\x0a\x14\xbd\x3a\x7e\xaf\x09\xa0\x28\x8a\x62\x27\ -\xf5\x8d\xcd\x7c\xff\x17\x0f\xd1\xe3\x55\x47\x68\x2f\x44\x08\xc1\ -\xd2\xf9\xd3\x0d\xef\x47\x4a\xfe\x52\x92\x2a\x3a\xfc\xd1\x96\xdf\ -\x52\x15\xa1\x69\xbf\x23\x48\x36\x03\x4a\x09\x7b\xaa\x04\xdd\xea\ -\x77\x42\x51\x94\x11\xaa\xa3\xa3\x8b\xef\xfe\xcf\x9f\xd5\x35\xbf\ -\x03\x34\x25\x7f\x34\x19\x29\x09\x86\xf7\xe3\x70\x68\x7f\xf6\x57\ -\x5b\x7e\x4b\x00\x8a\x53\xc4\x3e\x81\x7c\xdb\x5f\xed\xd9\x95\x94\ -\x50\x5a\x2d\xa8\x6a\x51\x4f\xff\x8a\xa2\x8c\x4c\x3d\x5e\x2f\xdf\ -\xfb\xe5\x83\x7c\x78\x5c\x6d\xfa\x1b\xa8\xcf\x2c\xbc\xd8\xf8\x4e\ -\x84\xdc\x34\x25\x45\xf8\xed\xf6\x32\xff\x2e\x56\x08\xfe\xe8\xd7\ -\xf6\x6c\xc6\x27\x61\x5b\x85\xa0\xa2\x49\x0d\xfe\x8a\xa2\x8c\x4c\ -\x52\x4a\x7e\xf1\x87\xc7\x29\xfd\xe0\x43\xab\x43\x09\x18\xb1\xd1\ -\x51\x5c\x72\x71\xa1\xe1\xfd\x48\xe9\xdf\x31\xd6\xaf\x09\x40\x64\ -\xbb\xe3\x29\xe0\xa4\x3f\xdb\xb4\x0b\x9f\x0e\xdb\xca\x05\x27\x5a\ -\xd5\xe0\xaf\x28\xca\xc8\x24\xa5\xe4\x57\x7f\x7a\x8a\x57\xdf\xd8\ -\x69\x75\x28\x01\x65\xc5\xe5\x33\x71\xbb\x0c\x3f\xfb\x5f\x1b\xda\ -\xe4\x78\xc6\x9f\x0d\xfa\x35\x01\xc8\xcd\x15\xdd\x23\x71\x16\xc0\ -\x27\x61\x4b\x99\x46\x5d\x9b\x1a\xfc\x15\x25\x18\x95\x35\x8e\xfc\ -\x0d\x4e\x52\x4a\xee\xba\xff\x69\x75\xdc\x6f\x90\x5c\x4e\x07\xcb\ -\x2f\x9d\x69\x78\x3f\x02\xfe\x50\x50\x20\x7a\xfc\xd9\xa6\xdf\xcf\ -\x2b\xe8\x3d\xda\x1f\x00\xbf\x06\x69\xb5\xbd\x27\x04\xa7\xfc\xb2\ -\xe7\x52\x51\x94\x40\x54\xd5\x22\xd8\x59\x29\xd0\x47\x68\x16\x70\ -\xfa\xc9\x7f\xcd\xab\x23\x7e\x1b\x97\xdf\x2d\x98\x55\x44\x5c\xac\ -\xc7\xe8\x6e\xba\x7b\x5d\xda\x7d\xfe\x6e\xd4\xef\x09\x40\x49\xb6\ -\xa8\x11\x92\x27\xfc\xdd\xae\x55\x7a\x7d\x50\xd6\xa8\x9e\xfc\x15\ -\x25\xd8\x55\x35\x0b\x36\x1f\xd7\x46\xdc\xe9\x1f\x9f\x4f\xe7\x67\ -\xbf\x7f\x4c\x3d\xf9\x0f\x81\x10\x82\xeb\xae\x9c\x6f\x7c\x3f\xf0\ -\xf7\xe9\x89\xe2\x84\xbf\xdb\x35\xa4\x62\x81\x4f\xd3\xee\x32\xa2\ -\x5d\x2b\x74\x7b\xfb\x96\x00\x14\x45\x51\x1a\x3a\x60\xe3\x11\x8d\ -\xda\x11\xb2\x17\xa8\xab\xbb\x87\x7f\xff\xe5\x03\xbc\xb4\x71\xbb\ -\xd5\xa1\x04\xa4\x99\x53\xf3\x19\x93\x95\x6a\x7c\x47\x52\xbb\xc7\ -\x88\x66\x0d\x49\x00\x4a\x52\x45\xa9\x40\x6e\x36\xa2\x6d\xb3\x85\ -\x3a\x51\x35\xff\x15\x45\xf9\x58\x97\x17\xb6\x96\x0b\xde\xad\x11\ -\xf4\xea\x56\x47\x33\x74\xad\xed\x1d\xdc\xf9\x93\xfb\xd8\xb2\x6b\ -\x9f\xd5\xa1\x04\xac\xeb\x97\x2f\x30\xa1\x17\xb1\xa1\x28\x5d\xec\ -\x31\xa2\x65\xc3\x6a\x16\xea\x82\x11\x31\x0b\xe0\x74\x40\x74\x98\ -\xd5\x51\x28\x8a\x62\x27\x12\x38\xd6\x20\x78\xed\x90\xc6\xa1\xfa\ -\xc0\x2b\x0a\x76\xbc\xe2\x04\x5f\xf9\xde\xdd\xbc\x77\xe0\xa8\xd5\ -\xa1\x04\xac\xa2\x82\xb1\x14\x8e\x1f\x6d\x78\x3f\x12\xfd\x6e\xa3\ -\xda\x36\xac\x9a\x7d\x71\x8a\x63\xed\xee\x6a\x7d\x1f\x90\x6f\x54\ -\x1f\x66\x49\x8e\x92\x34\x75\xaa\x79\x00\x45\x51\xce\xd6\xe3\x83\ -\x7d\xb5\x82\xfd\x75\x82\xa8\x10\x88\x09\x91\xb8\x9d\x7d\x0f\x0e\ -\x0e\x01\x2e\x0d\x22\x42\x20\x36\x4c\xe2\x34\xbc\x44\xfc\xc0\xbc\ -\xb9\xfd\x03\x7e\xf2\xdb\xbf\xd1\xd9\xd9\x6d\x75\x28\x01\xed\xc6\ -\xcf\x5e\x66\x46\x37\xfb\x8b\x53\x1d\x2f\x1a\xd5\xb8\x61\x09\x80\ -\x10\x42\x2f\xad\xf2\xfe\x1a\xc4\xfd\x46\xf5\x61\x96\x54\x8f\xe4\ -\x40\x9d\x4a\x00\x14\x45\xe9\x9f\x94\xd0\xd2\x05\x2d\x5d\xfd\x7f\ -\x4f\x84\x3a\x05\x33\x32\x75\x62\x2d\x9c\x4d\xd4\x75\xc9\xc3\x4f\ -\xbd\xca\x43\x4f\xae\x43\x4a\xb5\xb1\x69\x38\xa6\x4e\xca\xa5\x78\ -\x62\xae\xf1\x1d\x49\xf9\x73\x21\x84\x61\x0b\x4d\x86\xe6\xa4\x7a\ -\x8d\xe3\xaf\x40\x85\x91\x7d\x98\xc1\x13\x02\xd1\xa1\x56\x47\xa1\ -\x28\x4a\xa0\xea\xdb\x37\xa0\xd1\x65\xd1\x52\x41\x43\x53\x2b\xff\ -\xf2\xd3\xfb\x78\xf0\x89\x57\xd5\xe0\xef\x07\xb7\x5c\xbb\xd4\x8c\ -\x6e\x2a\x43\x9a\x1d\x8f\x19\xd9\x81\xa1\x09\x40\x49\x89\xe8\x15\ -\x60\xc8\xee\x45\xb3\x65\xc4\xa8\x5f\x1a\x45\x51\x86\xae\xdb\x0b\ -\x1f\x9c\x30\x7f\x26\x71\xfb\xbb\x07\xf9\xf2\x77\x7f\xc5\xf6\x3d\ -\x07\x4d\xef\x7b\x24\x9a\x55\x52\xc0\xc4\xbc\x6c\xc3\xfb\x11\x82\ -\x5f\xf9\xbb\xf0\xcf\x27\x19\xbe\x2a\xd5\xee\xd6\xee\x03\x1a\x8d\ -\xee\xc7\x68\x69\x1e\xa9\x4e\x03\x28\x8a\x32\x2c\x55\x2d\x82\x96\ -\x2e\x73\xfa\xea\xe8\xe8\xe2\x57\x7f\x7a\x92\xef\xfc\xf7\x7d\x34\ -\x34\xaa\x1b\xfd\xfc\x41\xd3\x34\x6e\xbd\xee\x0a\x33\xba\x6a\x70\ -\x3b\xb5\x07\x8c\xee\xc4\xf0\x04\x60\x76\x82\x68\x05\x7e\x6b\x74\ -\x3f\x46\x0b\x73\x41\x8c\x3a\x0d\xa0\x28\xca\x30\x48\x09\x47\x1b\ -\x8c\x7f\x94\xd8\xb6\xfb\x00\x5f\xb8\xf3\x97\xac\x5d\xb7\x45\x4d\ -\xf9\xfb\xd1\x55\x97\x5e\xcc\xe8\xcc\x14\xe3\x3b\x92\xdc\x5d\x90\ -\x28\xda\x8c\xee\xc6\xb0\x4d\x80\x67\xd2\xbb\xb4\x5f\x6b\xa1\xfa\ -\xb7\x80\x18\x33\xfa\x33\x4a\x5a\xb4\xa4\x51\x9d\x06\x50\x14\x65\ -\x18\x2a\x9b\x05\x05\x49\x12\x97\xc3\xff\x6d\x9f\x6a\x6a\xe1\x77\ -\x7f\x7d\x8e\x75\x6f\xee\xf2\x7f\xe3\x41\x2e\x22\x3c\x8c\x9b\xae\ -\xb9\xdc\xf8\x8e\x04\xcd\xa2\x47\x33\xe5\xa1\xd9\x94\x83\x29\x25\ -\x63\x44\x33\x82\x7b\xcd\xe8\xcb\x48\xd9\xb1\x12\x8f\xda\x0c\xa8\ -\x28\xca\x30\x78\x75\xfc\x7e\xb1\x58\xaf\xd7\xc7\xdf\x9f\xdd\xc0\ -\xb5\xdf\xfa\x99\x1a\xfc\x0d\xf2\xa5\x55\x8b\x88\x8d\x8e\x32\xbe\ -\x23\xc9\xaf\x8b\x72\x44\x93\xf1\x1d\x99\x34\x03\x00\x20\xba\xb5\ -\xff\x93\x6e\xfd\x36\x02\x78\x16\xc0\xa9\xc1\x9c\x1c\x9d\x3d\x55\ -\x82\xaa\x16\x35\x13\xa0\x28\xca\xd0\xd4\xb6\x41\x5a\xf4\xf0\xdb\ -\x91\x52\xb2\x79\xe7\x5e\xfe\xf0\xc8\xf3\x94\x57\xd7\x0d\xbf\x41\ -\xa5\x5f\x59\xe9\x49\x7c\xf6\x8a\x79\xc6\x77\x24\x68\x16\xdd\xc6\ -\x94\xfd\xed\x8f\x69\x09\x40\x51\x8e\x68\xda\x55\xe9\xfb\xbd\x10\ -\x7c\xdf\xac\x3e\x8d\xe0\xd2\xa0\x24\x43\x92\xd0\x00\xfb\xea\x04\ -\x3d\x3e\xab\x23\x52\x14\x25\xd0\xd4\xb5\x09\x24\xc3\xdb\x58\xfc\ -\xde\xfe\xa3\xdc\xf7\xf7\x97\x78\x77\xff\x11\xbf\xc5\xa5\x7c\x9a\ -\x10\x82\xef\xac\x5e\x85\xcb\x69\xc0\x9a\xcd\x27\xfb\x92\xdc\x65\ -\xd6\xd3\x3f\x98\x98\x00\x00\x78\xd1\x7e\xed\x42\xbf\x0d\x30\xfc\ -\xee\x44\x23\x09\x20\x3b\x4e\x92\xe2\x91\xec\xa9\x11\xd4\xa8\xd9\ -\x00\x45\x51\x06\xa1\xcb\xdb\x57\x38\x68\x28\xf5\x45\xb6\xbf\x7b\ -\x90\x47\xd6\xbc\xce\xee\xbd\x87\xfd\x1f\x98\xf2\x29\x8b\xe7\x95\ -\x98\x53\xf4\x07\x1a\xe9\xd1\x7e\x63\x46\x47\xa7\x99\x9a\x00\xcc\ -\x48\x17\xa7\x76\x57\xf9\x7e\x2d\xe1\xc7\x66\xf6\x6b\x94\x10\x27\ -\x4c\xcf\x90\x7c\x58\xdf\x57\x0e\x54\x51\x14\x65\xa0\xea\xdb\x05\ -\xd1\xa1\x03\xdb\xa1\xaf\xeb\x3a\x1b\xde\x79\x97\xbf\xaf\xdd\xc0\ -\xa1\x63\x95\x06\x47\xa6\x9c\xe6\x89\x8a\xe0\x1b\x5f\xbc\xd2\x94\ -\xbe\x84\xe0\x7f\xcd\x7c\xfa\x07\x93\x13\x00\x00\xb7\x4b\xfb\xbf\ -\xee\x5e\xfd\x6b\x40\x92\xd9\x7d\x1b\x41\x00\x79\xf1\x92\xce\x1e\ -\x38\xd6\xa8\x92\x00\x45\x51\x06\xa6\x75\x00\x25\x5e\x1a\x1a\x5b\ -\x78\xf9\x8d\x1d\xac\x5d\xb7\x85\x9a\xba\x06\xe3\x83\x52\xce\x72\ -\xe7\xea\x55\xc4\x78\x22\xcd\xe8\xaa\xae\xdd\xa5\x99\xbe\x51\xde\ -\xf4\x04\xa0\x20\x51\xb4\xed\xaa\xf6\xfd\x5c\xc8\x91\x71\x5b\xe0\ -\x69\x13\x53\x24\x4d\x5d\x82\xc6\x4e\xab\x23\x51\x14\x25\x10\xb4\ -\x75\x0b\xfa\xee\x15\x3c\x9b\xd7\xe7\x63\xdb\xee\x03\xbc\xbc\x69\ -\x3b\x9b\x77\xec\xc5\xeb\x53\x1b\x8d\xac\x30\x77\xc6\x24\x16\xcd\ -\x2a\x32\xa5\x2f\x09\xff\xf5\x51\xcd\x1c\x53\x99\x9e\x00\x00\x84\ -\x36\x6a\xbf\xef\x8e\xd1\xbf\x05\xe4\x58\xd1\xbf\x11\x1c\x02\x8a\ -\xd3\x74\x36\x1e\xd1\xd0\x55\xdd\x0d\x45\x51\x2e\xa0\xf5\x8c\xcb\ -\xf8\x74\x5d\xe7\xbd\x03\xc7\xd8\xb0\x65\x0f\xeb\xb7\xec\xa6\xb9\ -\xa5\xdd\xba\xc0\x14\xa2\x3d\x11\xfc\xcb\x2d\x9f\x35\xab\xbb\xe3\ -\x9e\x0e\xcd\x92\x4b\xf3\x2c\x49\x00\x0a\x0a\x44\x4f\x69\x95\xf7\ -\xbf\x41\x18\x5e\xea\xd0\x4c\x51\x21\x30\x3e\x51\xaa\xfd\x00\x8a\ -\xa2\x5c\x50\x6b\x7b\x27\x6f\x6c\x3d\xc8\xe6\x9d\x1f\xb0\xa5\x74\ -\x9f\x1a\xf4\x6d\xe4\x3b\xb7\x7c\x96\xb8\x18\x13\xce\xfc\x03\x48\ -\xf9\xa3\xdc\x5c\x61\xc9\xdd\xcc\x96\x24\x00\x00\x87\x53\x1d\x7f\ -\x19\x5b\xad\x7f\x1b\x28\xb4\x2a\x06\x23\x8c\x8d\x97\x54\x36\x9b\ -\x57\xef\x5b\x51\x94\xc0\xd0\xd9\xd1\x46\x4d\x65\x19\x95\x65\x47\ -\x29\x3f\x76\x88\xda\xea\x4a\xa4\x34\xec\xa6\x57\x65\x88\x96\x2d\ -\x9c\xc1\x82\x8b\x27\x9b\xd5\xdd\x9e\xc3\x69\x8e\x47\xcd\xea\xec\ -\x93\x2c\x4b\x00\xae\x11\xc2\xb7\xa7\xb2\xf7\x76\x5d\x68\x1b\xac\ -\x8a\xc1\x08\x1a\x30\x25\x45\xe7\xad\x63\x5a\x3f\xab\x7b\x8a\xa2\ -\x8c\x74\x5e\x6f\x2f\x2d\x4d\x0d\xd4\xd7\x9d\xe0\x54\x5d\x0d\xf5\ -\x75\x35\x9c\xa8\xae\xa0\xb9\xf1\x94\xd5\xa1\x29\x17\x90\x99\x9a\ -\xc8\xb7\x6f\x5c\x69\x5e\x87\x9a\x76\xfb\x35\x42\x58\xb6\xc9\xc3\ -\xb2\x04\x00\x60\x4a\xba\x6b\xe3\xae\x2a\x7d\xad\x40\x2e\xb7\x32\ -\x0e\x7f\x8b\x0b\x87\xcc\x18\x49\x59\x93\x5a\x0a\x50\x94\x91\xe0\ -\x83\x3d\xdb\xa9\xad\xa9\x38\xeb\x67\x5e\x6f\x2f\x3d\xdd\xdd\x74\ -\x76\x76\xd0\xdd\xd9\x4e\x7b\x5b\x2b\x2d\x4d\x8d\xb4\xb7\xa9\x9b\ -\xf7\x02\x91\xdb\xe9\xe4\xc7\xb7\x7f\x81\xb0\x50\xb7\x29\xfd\x09\ -\x78\xa2\x28\x45\xbc\x61\x4a\x67\xe7\x60\x69\x02\x00\xa0\x3b\xc5\ -\x77\x1c\x5e\xb9\x04\x08\xb1\x3a\x16\x7f\xca\x4f\x92\xd4\xb4\xaa\ -\x4a\x81\x8a\x32\x12\xec\x7f\x6f\x27\xfb\xad\x0e\x42\x31\xd4\xb7\ -\x6e\x5a\x41\xde\xe8\x74\xb3\xba\xeb\x42\xd3\xfe\xcd\xac\xce\xce\ -\xc5\x94\xcb\x80\xce\x67\x5a\x92\x38\x2a\xc0\xd4\xea\x47\x66\x08\ -\x71\x42\x7e\xa2\x5a\x04\x50\x14\x45\xb1\xbb\x4b\xe7\x14\xb3\xfc\ -\xb2\x99\xe6\x75\x28\xf8\x55\x51\x8a\x38\x6e\x5e\x87\xfd\xb3\x3c\ -\x01\x00\x68\x77\x6b\xff\x8d\xa0\xc6\xea\x38\xfc\x2d\x2b\x56\x12\ -\x1b\x66\x75\x14\x8a\xa2\x28\xca\xb9\x8c\xce\x4c\xe5\xdf\xbe\xfa\ -\x39\x33\xbb\xac\x0a\x71\x6a\xbf\x30\xb3\xc3\x73\xb1\x45\x02\x30\ -\x3b\x41\xb4\x22\xe5\x7f\x58\x1d\x87\xbf\x09\x01\x93\x53\x74\x84\ -\xda\x0a\xa0\x28\x8a\x62\x3b\x31\x9e\x48\x7e\xf6\x6f\x37\x12\x1a\ -\x62\xce\xba\x3f\x80\x14\xf2\x7b\x05\x89\xa2\xcd\xb4\x0e\xcf\xc3\ -\x16\x09\x00\x40\x51\xaa\xe3\x21\xa4\xdc\x61\x75\x1c\xfe\x16\x13\ -\xd6\x37\x13\xa0\x28\x8a\xa2\xd8\x87\xdb\xe5\xe2\x67\xff\x76\x13\ -\x69\x49\xf1\xa6\xf5\x29\x84\xdc\x56\x9c\x62\xdd\xb1\xbf\x4f\xb2\ -\x4d\x02\x20\x84\xd0\x85\x70\xdc\x4e\x7f\xb5\x31\x03\x5c\x7e\xa2\ -\xc4\x6d\xfc\x4d\x92\x8a\xa2\x28\xca\x00\x08\x21\xf8\xf7\xaf\x7f\ -\x9e\x49\xe3\x4c\x2d\x46\x2b\x7d\x38\xfe\x45\x08\x61\x9b\x31\xce\ -\x36\x09\x00\x40\x51\x9a\xd8\x22\xe0\x49\xab\xe3\xf0\x37\xb7\x03\ -\x26\x24\xd9\xe6\xdf\x5c\x51\x14\x25\xa8\x7d\xed\xfa\x65\x5c\x3a\ -\xa7\xd8\xdc\x4e\x25\x7f\x2b\x49\x15\x9b\xcd\xed\xf4\xfc\x6c\x95\ -\x00\x00\x48\xa1\xfd\x2b\x30\xe2\xae\xd4\xc9\x8e\x91\x78\x46\xd4\ -\x41\x47\x45\x51\x94\xc0\xb3\x6c\xe1\x0c\xae\x5b\xbe\xc0\xec\x6e\ -\xdb\x34\xa9\xfd\xbb\xd9\x9d\x5e\x88\xed\x12\x80\xe2\x54\x51\x06\ -\xfc\xa7\xd5\x71\xf8\x9b\x10\x50\xa0\x66\x01\x14\x45\x51\x2c\x33\ -\xa3\x68\x3c\xdf\xfd\xca\x35\xa6\xf7\x2b\x05\xff\x31\x25\x43\x54\ -\x99\xde\xf1\x05\xd8\x2e\x01\x00\x68\x4e\xd5\xfe\x0f\xd8\x65\x75\ -\x1c\xfe\x96\x14\x25\x89\x0b\xb7\x3a\x0a\x45\x51\x94\xe0\x33\x6e\ -\x4c\x06\x3f\xb9\xf3\xcb\x38\x1c\x26\x0f\x7b\x52\xee\x38\x92\xa2\ -\xfd\xd6\xdc\x4e\x07\xc6\x96\x09\xc0\x7c\x21\xbc\x48\xed\x66\x04\ -\xbd\x56\xc7\xe2\x6f\x93\x92\x75\xd4\xa9\x40\x45\x51\x14\xf3\xe4\ -\x66\xa7\x71\xd7\x0f\x6f\x25\x3c\xcc\xf4\x75\x58\xaf\x26\x1c\xb7\ -\x5a\x59\xef\xff\x7c\x6c\x99\x00\x00\x14\xa7\x8b\x77\x85\xe4\x2e\ -\xab\xe3\xf0\xb7\xd8\xb0\xbe\x99\x00\x45\x51\x14\xc5\x78\x63\xb3\ -\xd2\xf8\xcd\x8f\xbe\x86\x27\x2a\xc2\xfc\xce\x05\x3f\x9f\x92\x26\ -\x76\x9b\xdf\xf1\xc0\xd8\x36\x01\x00\xe8\xd4\xb5\x1f\x03\x87\xad\ -\x8e\xc3\xdf\xf2\x13\xa5\x2a\x0e\xa4\x28\x8a\x62\xb0\x31\x59\xa9\ -\xd6\x0d\xfe\x70\xa8\xb9\x5b\xfb\x1f\x2b\x3a\x1e\x28\x5b\x27\x00\ -\x33\x33\x44\xa7\xd0\xf5\x5b\x18\x61\xb5\x01\x3c\xa1\x90\xe1\x19\ -\x51\xff\x97\x14\x45\x51\x6c\x25\x33\x2d\x89\xbb\x7e\x78\x2b\xd1\ -\x1e\x4b\x06\x7f\x29\xa5\xfe\xb5\xf9\x39\xa2\xcb\x8a\xce\x07\xca\ -\xd6\x09\x00\x40\x51\x86\x6b\x13\xf0\x90\xd5\x71\xf8\xdb\xf8\x44\ -\x89\x43\xcd\x02\x28\x8a\xa2\xf8\x5d\x66\x5a\x12\xf7\xfe\xf8\xeb\ -\xc4\xc5\x7a\xac\x09\x40\x70\xdf\xd4\x74\xd7\x06\x6b\x3a\x1f\x38\ -\xdb\x27\x00\x00\x7a\x97\x76\x27\x60\xbb\x23\x14\xc3\x11\xee\x86\ -\xec\x38\x35\x0b\xa0\x28\x8a\xe2\x4f\x79\x39\xe9\xdc\xfb\x9f\xdf\ -\xb0\x72\xf0\xaf\x11\xdd\xf6\x3b\xf3\xdf\x9f\x80\x48\x00\x4a\xc6\ -\x88\x66\x21\xe5\x1d\x56\xc7\xe1\x6f\x79\xf1\x12\xa7\x2a\x11\xac\ -\x28\x8a\xe2\x17\x25\x85\x79\x7d\x83\x7f\x4c\x94\x65\x31\x48\xe4\ -\x37\x8a\x72\x44\x93\x65\x01\x0c\x42\x40\x24\x00\x00\x45\xe9\xce\ -\x27\x25\x62\xad\xd5\x71\xf8\x53\x88\x13\xc6\x8e\x52\xb3\x00\x8a\ -\xa2\x28\xc3\xb5\xe4\x92\x69\xfc\xea\x07\x5f\x21\x3c\x3c\xd4\xba\ -\x20\x84\x78\x6a\x6a\xaa\xf3\x19\xeb\x02\x18\x9c\x80\x49\x00\x00\ -\x64\xaf\xf8\x3a\x50\x6f\x75\x1c\xfe\x94\x1b\x2f\x89\x30\xef\x26\ -\x4a\x45\x51\x94\x11\xe7\xfa\xe5\x0b\xf9\xfe\x37\xae\xc5\xe9\xb0\ -\x74\x4a\xb5\xce\xe5\x10\xb7\x59\x19\xc0\x60\x05\x54\x02\x50\x92\ -\x2d\x6a\x04\x72\xb5\xd5\x71\xf8\x93\x43\xc0\xc4\x64\x35\x0b\xa0\ -\x28\x8a\x32\x58\x9a\xa6\xf1\xdd\xaf\x7c\x96\xaf\xdd\xb0\x0c\x61\ -\xed\xd9\x6a\x29\x91\xab\x27\x25\x89\x5a\x2b\x83\x18\xac\x80\x4a\ -\x00\x00\x8a\xd2\x9c\xcf\x0a\xc9\x9f\xac\x8e\xc3\x9f\x52\xa2\x24\ -\x49\x91\x2a\x09\x50\x14\x45\x19\xa8\xb0\xb0\x10\xfe\xf7\xdf\x6f\ -\xe1\xaa\xcb\x66\x5a\x1d\x0a\x02\x7e\x3f\x35\xcd\xf9\xbc\xd5\x71\ -\x0c\x56\xc0\x25\x00\x00\x3e\x4d\xbb\x03\x38\x60\x75\x1c\xfe\x34\ -\x29\x45\x1d\x0b\x54\x14\x45\x19\x88\xcc\xd4\x44\xfe\xf4\xd3\xdb\ -\x99\x51\x34\xde\xea\x50\x00\xf6\xfb\xfa\x6e\xb1\x0d\x38\x01\x99\ -\x00\x94\xa4\x8a\x0e\x5d\x68\xd7\x03\x3d\x56\xc7\xe2\x2f\x91\xee\ -\xbe\xfd\x00\x8a\xa2\x28\xca\xb9\xcd\x9d\x3e\x91\x3f\xff\xfc\x76\ -\x72\x32\x92\xad\x0e\x05\xa0\x5b\x48\xed\xba\x92\x54\xd1\x61\x75\ -\x20\x43\x11\x90\x09\x00\x40\x49\xaa\x28\x45\xf0\x23\xab\xe3\xf0\ -\xa7\x71\x09\xea\xb6\x40\x45\x51\x94\xfe\x38\x1c\x1a\x5f\xbb\x61\ -\x19\x3f\xfd\xd7\x9b\x89\x08\x0f\xb3\x3a\x9c\x3e\x92\xef\x17\xa5\ -\x8b\x3d\x56\x87\x31\x54\x01\x9b\x00\x00\x14\xa5\x68\xbf\x04\x61\ -\xfb\x6a\x4b\x03\x25\x04\x94\xa4\xe9\xb8\x02\xfa\x5f\x45\x51\x14\ -\xc5\xbf\xe2\x63\xa3\xf9\xed\x7f\x7e\x83\xeb\x97\x2f\xb4\x3a\x94\ -\x33\x88\xd7\x8a\xd2\xb4\xbb\xad\x8e\x62\x38\x02\x7a\xa8\x11\x42\ -\xe8\x9a\x2e\xbe\x08\x34\x58\x1d\x8b\xbf\x84\xbb\x61\x6a\xba\xba\ -\x2c\x48\x51\x14\x05\xa0\x78\x62\x2e\x0f\xfe\xef\x77\x28\x1c\x3f\ -\xda\xea\x50\xce\xd4\xe8\x45\xdc\x2c\x84\xd0\xad\x0e\x64\x38\x02\ -\x3a\x01\x00\x98\x92\x21\xaa\xa4\x90\xb7\x58\x1d\x87\x3f\x25\x47\ -\x49\x72\x55\x81\x20\x45\x51\x82\x98\xdb\xe5\xe2\x6b\x37\x2c\xe3\ -\xee\xff\xf7\x55\x4b\x2b\xaa\xe2\xe9\xdb\x00\x00\x1a\x00\x49\x44\ -\x41\x54\xfb\xf5\x47\x48\x79\xeb\xf4\x34\x51\x61\x75\x1c\xc3\x15\ -\xf0\x09\x00\xc0\xd4\x54\xe7\x1a\x09\x0f\x5b\x1d\x87\x3f\x4d\x48\ -\x94\x24\x44\xa8\x24\x40\x51\x94\xe0\x33\x26\x2b\x95\x3f\xfd\xfc\ -\xdb\x5c\xbf\x7c\x21\x9a\x66\xaf\x61\x4a\x48\xfe\x54\x94\xee\x7c\ -\xd2\xea\x38\xfc\xc1\x69\x75\x00\xfe\x12\xea\xd2\xbe\xd9\xdd\xab\ -\x4f\x07\xf2\xad\x8e\xc5\x1f\x84\x80\xe2\x74\xc9\xc6\xc3\x82\x1e\ -\x9f\xd5\xd1\x28\x81\x2a\x26\x2e\x1e\x4f\x4c\xac\xd5\x61\x28\xca\ -\x80\x38\x1d\x0e\xbe\xb8\xea\x52\xbe\xb4\xea\x52\x1c\x0e\x7b\x0d\ -\xfc\x00\x08\xde\xd7\x1c\xda\x9d\x56\x87\xe1\x2f\x23\x26\x01\x28\ -\x48\x14\x6d\x7b\x2a\xe5\x0a\x5d\xd3\xb7\x23\x89\xb6\x3a\x1e\x7f\ -\x08\x73\xc2\xe4\x54\xc9\x8e\x0a\xb5\x21\x40\x19\x1c\xa7\xcb\xc5\ -\xf4\xd9\x0b\x99\x3e\x7b\x11\x4e\xa7\xcb\xea\x70\x14\xe5\x82\xb2\ -\x33\x92\xf9\xe1\x6d\xd7\x32\x7e\x4c\xa6\xd5\xa1\x9c\x4b\x93\xf4\ -\x69\x2b\x27\xa7\x8a\x76\xab\x03\xf1\x97\x11\x93\x00\x00\x4c\x49\ -\x17\x87\x4a\xab\xbc\x5f\xa4\xef\xd2\xa0\x11\x31\x6a\xa6\x79\x24\ -\x47\x23\x04\xa7\x46\xcc\x47\x4e\x31\x5a\xee\x84\x42\xe6\x2f\x5e\ -\x81\x27\x26\xce\xea\x50\x14\xe5\x82\xdc\x4e\x27\x9f\xbf\x6a\x3e\ -\x5f\xbe\xfa\x52\xdc\x2e\xdb\x26\xab\x52\x0a\x79\xd3\xd4\x0c\x71\ -\xd8\xea\x40\xfc\x69\x44\x25\x00\x00\xc5\x69\xce\xe7\x4a\x2b\x7d\ -\x3f\x47\x10\x10\xf7\x31\x0f\xc4\xc4\x24\x9d\x37\x8f\x6a\xa8\x1d\ -\x01\xca\xf9\xc4\xc6\xc5\x33\x7f\xe9\x2a\x46\xe7\x8e\x88\x55\x30\ -\x25\x08\x14\x17\x8c\xe5\xce\xd5\xab\xc8\xb6\x47\x51\x9f\xf3\xf9\ -\x49\x20\xdd\xf2\x37\x50\x23\x2e\x01\x00\x28\x4a\xd3\x7e\xb8\xbb\ -\x5a\x16\x81\x5c\x6c\x75\x2c\xfe\x10\x1b\x06\x31\x61\xd0\xd8\x69\ -\x75\x24\x8a\x1d\xb9\x5c\x6e\xa6\xcd\x5e\xc0\x8c\x39\x97\xe2\x70\ -\x8c\xc8\x5f\x69\x65\x84\x89\x8f\x8d\xe6\xab\x37\x2c\x63\xf1\xbc\ -\x12\xab\x43\x19\x00\xf1\xda\xe1\x54\xf1\x5f\x56\x47\x61\x84\x11\ -\xf9\x6d\x21\x84\xd0\xf7\x56\xc8\xeb\xbb\x35\xb9\x13\xc8\xb1\x3a\ -\x1e\x7f\x48\x8a\x92\x34\x76\x8e\x88\x55\x0d\xc5\x8f\xc6\xe4\x15\ -\xb0\xf0\x8a\xab\xd5\x74\xbf\x12\x10\x1c\x0e\x8d\x95\x97\xcf\xe6\ -\x96\x6b\x97\x12\x1e\x16\x62\x75\x38\x03\x51\xa6\x0b\x71\xdd\x35\ -\x42\x8c\xc8\xad\xd8\x23\x32\x01\x00\x28\xc8\x10\x0d\xbb\x2b\xe5\ -\x4a\x29\xf4\x2d\x80\x4d\xea\x46\x0e\x5d\x6c\xa8\xd5\x11\x28\x76\ -\x32\x2a\x31\x99\x85\x4b\x56\x91\x39\x3a\xcf\xea\x50\x14\x65\x40\ -\x2e\x2a\x9e\xc0\x6d\x5f\xb8\x32\x10\xa6\xfb\x4f\xeb\x92\x42\x5b\ -\x55\x92\x2a\xea\xad\x0e\xc4\x28\x23\x36\x01\x00\x28\x4a\x17\x7b\ -\x4a\xab\xbd\xb7\x22\xc5\x5f\xad\x8e\x65\xb8\x3c\xa1\x92\x11\xb2\ -\xaf\x51\x19\x06\x97\xdb\xcd\xb4\x59\x6a\xba\x5f\x09\x1c\xf9\xb9\ -\x59\x7c\xf5\x86\x65\x14\x17\x8c\xb5\x3a\x94\x41\x91\xc8\xaf\x4f\ -\x4d\x15\xbb\xac\x8e\xc3\x48\x23\xfe\x1b\xa4\x38\xd5\xf9\x48\x69\ -\xa5\x6f\x16\x82\x5b\xad\x8e\x65\x38\xc2\x5c\x10\xe1\x86\xf6\x11\ -\x73\xff\xa1\x32\x58\x63\xf2\x0a\x58\xb8\xec\xb3\x78\xa2\xd5\xb9\ -\x7e\xc5\xfe\xb2\xd2\x93\xb8\xf9\x73\x4b\x98\x7f\x51\x21\x22\xf0\ -\x6a\x9b\xdf\x3b\x35\xcd\xf9\x90\xd5\x41\x18\x6d\xc4\x27\x00\x00\ -\x51\x9d\xda\xb7\xdb\x22\x7c\x53\xa4\x14\x33\xac\x8e\x65\x38\xb2\ -\x62\x24\xfb\xea\x02\xee\x17\x49\x19\xa6\xd8\x51\x89\x2c\xba\xe2\ -\x6a\xb2\xc6\x8c\xb3\x3a\x14\x45\xb9\xa0\xa4\xf8\x58\xbe\xb4\xea\ -\x52\x96\x2d\x9c\x61\xbb\x2a\x7e\x03\x23\xb7\x84\x34\x39\xbe\x63\ -\x75\x14\x66\x08\x8a\x04\x20\x37\x57\x74\x6f\xaf\x93\xcb\x9d\xbd\ -\xfa\x56\x20\xcb\xea\x78\x86\x6a\x6c\xbc\xa4\xa2\x59\xd0\xda\x6d\ -\x75\x24\x8a\x19\xdc\xa1\x61\xcc\x9a\xbf\x84\xa2\xe9\x73\x02\xf4\ -\x8b\x54\x09\x26\x69\x49\xf1\x5c\x77\xd5\x7c\x96\x2e\x98\x81\xcb\ -\xe9\xb0\x3a\x9c\xa1\x3a\xe6\x72\x3a\x56\x16\x14\x88\xa0\x98\x6b\ -\x0d\x8a\x04\x00\x60\x7a\xa2\x38\xb1\xab\x5a\x2e\x11\x52\x7f\x1b\ -\x08\xc8\x39\x54\x4d\x40\x71\x9a\xce\xdb\xc7\x35\xbc\x01\x7d\x07\ -\x95\x72\x5e\x42\x90\x5f\x58\xc2\x25\x97\x5d\x45\x78\xa4\xbd\x2e\ -\x41\x51\x94\x4f\x1a\x93\x95\xca\xb5\x57\xce\xe7\xd2\xd9\xc5\xf6\ -\x2c\xdf\x3b\x50\x82\x66\x87\xa6\x5d\x39\x29\x49\xd4\x5a\x1d\x8a\ -\x59\x82\x26\x01\x00\x98\x9a\x2a\xf6\x97\xd6\xc8\x15\xe8\xfa\xab\ -\x40\x40\x9c\x41\xf9\xa4\xd8\x30\x98\x9e\x21\xd9\x56\x2e\xf0\xa9\ -\xca\x40\x23\x4e\x62\x72\x1a\x0b\x96\x5e\x4d\x7a\x96\xad\xae\x3e\ -\x55\x94\x4f\x29\x1c\x3f\x9a\xeb\x97\x2f\x60\xe6\xd4\xfc\x40\x5c\ -\xe3\x3f\x9b\xa0\x17\x5d\x5f\x35\x39\xd5\xf1\x81\xd5\xa1\x98\x29\ -\xa8\x12\x00\x80\xe2\x14\xf1\x46\x69\x95\xf7\x46\x10\x8f\x12\xa0\ -\xdb\xea\x13\x23\x25\xd3\x33\x61\x47\xa5\xc0\x3b\x22\x4f\xa7\x06\ -\x9f\x90\xd0\x70\xe6\x2c\xba\x82\xc9\x25\x33\x11\x22\x80\x9f\xa2\ -\x94\x11\xcd\xe1\xd0\x98\x3d\x6d\x22\xd7\x5e\x39\x9f\x89\x79\xd9\ -\x56\x87\xe3\x2f\x12\x5d\xae\x2e\x4e\x77\xad\xb7\x3a\x10\xb3\x05\ -\x5d\x02\x00\x50\x9c\xe6\xfc\x47\x69\x95\x6f\x3c\xf0\xff\xac\x8e\ -\x65\xa8\x92\x22\x25\x73\x73\x24\xef\x94\x69\x74\xf6\x5a\x1d\x8d\ -\x32\x64\xa7\xa7\xfb\x2f\xbf\x8a\xf0\x08\x35\xdd\xaf\xd8\x53\x5c\ -\xac\x87\x25\xf3\xa6\xb1\xe2\xf2\x99\x24\x27\x8c\xb0\xa2\x53\x92\ -\x1f\x15\xa7\x3b\x03\xfe\xa8\xf8\x50\x04\x65\x02\x00\x50\x94\xaa\ -\xfd\x78\x77\x8d\x9e\x8d\xe4\x8b\x56\xc7\x32\x54\x9e\x10\x98\x37\ -\x5a\xa7\xb4\x4a\x50\xd7\x16\x90\x93\x19\x41\x2d\x29\x25\x9d\x85\ -\x57\x7c\x96\xd4\x8c\x6c\xab\x43\x51\x94\x7e\x8d\x1b\x93\xc1\x67\ -\x97\xce\x65\xd1\xec\x22\x9c\x8e\x80\xdd\xd8\x77\x3e\xff\x28\x4a\ -\xd3\xfe\xdb\xea\x20\xac\x12\xb4\x09\x80\x10\x42\xee\xdc\x29\x57\ -\x6b\x29\x32\x0d\xe4\x42\xab\xe3\x19\xaa\x50\x27\x5c\x9c\x25\x29\ -\x6b\x80\xf7\x6b\x05\x3e\xb5\x39\xd0\xf6\x42\xc3\x23\x98\xbb\x68\ -\x19\x93\x8a\x2f\x0e\xfc\xb5\x53\x65\xc4\x89\xf1\x44\x72\xd9\x9c\ -\x62\xae\xba\x74\x26\x59\xe9\x49\x56\x87\x63\x1c\x29\xdf\x88\xea\ -\x74\xdc\x28\x84\x08\xda\xdd\x54\x41\x9b\x00\x00\x94\x94\x88\xde\ -\x9d\x47\xe4\x2a\x11\x22\x37\x0b\xc1\x44\xab\xe3\x19\x2a\x01\x64\ -\xc7\x49\xe2\x23\x24\xbb\x6b\x34\x75\x75\xb0\x4d\x09\x21\x98\x50\ -\x58\xc2\xfc\xc5\xcb\x09\x0b\x8f\xb4\x3a\x1c\x45\xf9\x98\xdb\xe9\ -\xa4\x64\x72\x1e\x8b\x2f\x99\xce\x9c\x69\x13\x03\xf9\x18\xdf\x40\ -\xed\x73\xfa\x1c\x2b\x72\x73\x45\x50\x1f\xaa\x0e\xea\x04\x00\xa0\ -\x64\x8c\x68\xde\x5d\x23\x3f\x23\x75\x7d\x2b\x10\xd0\xe9\x6e\x64\ -\x08\xcc\xce\xd6\x29\x6b\x14\xec\xab\x15\xf4\xa8\x0d\x82\xb6\x91\ -\x94\x9a\xc1\xa2\x2b\x3e\x4b\x4a\x7a\xc0\x96\xa1\x50\x46\xa0\x71\ -\x63\x32\xb8\x7c\x6e\x09\x97\xcf\x99\x4a\xb4\x27\xc2\xea\x70\xcc\ -\x72\x02\xa1\x2d\x2d\xcc\x12\x8d\x56\x07\x62\xb5\xa0\x4f\x00\x00\ -\x8a\x52\xc4\xf1\x5d\xd5\xf2\x0a\x81\xbe\x1e\x49\xb4\xd5\xf1\x0c\ -\x87\x00\xb2\x63\x25\xa9\x1e\xc9\xc1\x93\x82\x63\xa7\x04\x6a\x55\ -\xc0\x3a\xe1\x11\x51\xcc\xbd\xf4\x33\x4c\x9c\x32\x1d\xd4\x74\xbf\ -\x62\x31\x4d\x13\x4c\x18\x93\xc5\xbc\x19\x93\x98\x3f\x73\x0a\x29\ -\x89\x23\x6c\x43\xdf\x85\x35\x09\xa9\x2d\x29\x4a\x13\x65\x56\x07\ -\x62\x07\x2a\x01\xf8\xc8\xd4\x54\xb1\xab\xb4\x52\x2e\x41\xe8\xeb\ -\x80\x80\x9f\x9f\x75\x3b\x60\x52\xb2\x24\x27\xb6\xaf\x7c\x70\x4d\ -\x8b\x20\x68\x17\xba\x2c\xf0\xf1\x74\xff\xe5\x2b\x08\x8b\x08\x9a\ -\x27\x2b\xc5\x86\x34\x4d\x63\x62\x5e\x36\x97\x5c\x3c\x99\xf9\x17\ -\x4f\x26\x21\x2e\xa0\x9f\x71\x86\xa3\x43\x0a\xed\xca\xe2\x34\xb1\ -\xc7\xea\x40\xec\x42\x25\x00\x67\x28\x4e\x17\xef\x94\x56\xf6\x2e\ -\x47\x68\x2f\x00\x23\xe2\x02\xde\xc8\x90\xbe\xc2\x41\x2d\x5d\x7d\ -\x33\x02\xd5\x2a\x11\x30\x5c\x7a\xd6\x68\x16\x5e\x71\x35\x09\x49\ -\x69\x56\x87\xa2\x04\xa9\x88\xf0\x30\x4a\x0a\x73\x99\x33\x6d\x12\ -\xb3\x4a\xf2\x89\x8a\x08\xb7\x3a\x24\xab\x75\x6a\x52\x5f\x36\x25\ -\xcd\xf1\x96\xd5\x81\xd8\x89\x4a\x00\x3e\xa1\x38\xdd\xb5\x7e\x57\ -\x75\xef\x72\x21\xb5\x67\x09\xd0\x6a\x81\xfd\xf1\x84\xc2\xb4\x0c\ -\x49\x43\x87\xe4\xc0\x49\xc1\xc9\x36\x95\x08\xf8\x5b\x44\x94\x87\ -\x79\x8b\xae\x24\x7f\x72\x89\x9a\xee\x57\x4c\xa5\x69\x82\xdc\x9c\ -\x74\x4a\x26\xe5\x31\xad\x30\x8f\xc9\xf9\x63\x82\x61\x23\xdf\xc0\ -\x08\x7a\xa5\x2e\xaf\x99\x92\xee\xda\x68\x75\x28\x76\xa3\x12\x80\ -\x7e\x4c\x4d\x75\xbd\xba\xab\xda\x7b\x9d\x90\xe2\x71\x46\xd8\x7f\ -\xa3\xb8\x70\x98\x99\x25\x69\xed\x96\x94\x37\x0a\xaa\x5a\x04\x1d\ -\xaa\x90\xd0\xb0\x68\x0e\x07\xc5\xd3\xe7\x30\x73\xfe\x12\xdc\x21\ -\x23\x62\xe2\x48\x09\x00\x29\x89\x71\x14\x15\x8c\x65\xc6\x94\x09\ -\x4c\x2b\xcc\xc5\x13\xa5\x96\x9a\xfa\xe1\x43\xca\x2f\x4e\x4d\x77\ -\xbe\x60\x75\x20\x76\x34\xa2\x06\x37\x7f\x9a\x9a\xea\x5c\xb3\xbb\ -\xd2\xbb\x5a\x0a\xf1\x20\x30\xe2\x6a\xb3\x46\x85\x40\x41\xb2\x24\ -\x3f\x59\xd2\xd4\x09\x95\xcd\x82\xca\x66\x41\xb7\xd7\xea\xc8\x02\ -\x4b\x46\xf6\x58\x16\x2e\x5d\x45\x7c\x52\xaa\xd5\xa1\x28\x23\x5c\ -\x6a\xd2\x28\x26\x8d\xcf\x61\xf2\xf8\xd1\x94\x14\xe6\x91\x9a\x34\ -\xca\xea\x90\xec\x4e\x0a\x21\xbf\x5a\x94\xea\x7c\xcc\xea\x40\xec\ -\x4a\x25\x00\xe7\x51\x94\xee\xfc\xcb\xae\x2a\x5f\xa4\x80\x7b\xad\ -\x8e\xc5\x28\x82\xbe\x0b\x86\x62\xc3\x24\x05\x49\x7d\xb3\x02\x07\ -\xeb\x85\x2a\x2f\x7c\x01\x91\x9e\x68\x2e\xb9\x7c\x39\xe3\x27\x16\ -\x5b\x1d\x8a\x32\x02\x85\x87\x87\x32\x3e\x27\x9d\xf1\xb9\x99\x14\ -\x8e\x1b\x4d\xe1\x84\x1c\x3c\x91\x41\xbf\x8e\x3f\x18\x52\xc0\x6d\ -\x45\xa9\xce\xfb\xad\x0e\xc4\xce\x54\x02\x70\x01\x53\xd3\x1c\xbf\ -\xdb\x55\xed\x73\x09\xc9\x5d\x56\xc7\x62\x34\x4d\xf4\x15\x14\xca\ -\x88\xe9\xdb\x27\x70\xf8\x94\x40\xaa\x8d\x02\x67\xd1\x1c\x0e\xa6\ -\x94\xcc\x66\xf6\xc2\xa5\x6a\xba\x5f\xf1\x0b\xa7\xc3\xc1\x98\xec\ -\x54\x0a\xc7\xe5\x30\x6e\x4c\x06\xe3\x46\xa7\x93\x95\x96\x84\xa6\ -\xa9\x7d\x24\xc3\xf0\xef\x45\x69\x8e\xdf\x5b\x1d\x84\xdd\xa9\x04\ -\x60\x00\xa6\xa6\x3a\xee\x2e\xad\xf6\x8d\x42\xf2\x43\xab\x63\x31\ -\x83\x43\x83\x82\xa4\xbe\x5a\x02\xa5\x55\x1a\xad\x41\x5d\x2b\xeb\ -\x9f\x32\xb2\x73\x59\xb4\xec\x6a\x46\x25\x24\x5b\x1d\x8a\x12\x80\ -\x1c\x0e\x8d\xa4\xf8\x58\xb2\xd3\x93\xc8\xc9\x48\x21\x27\x23\x99\ -\x9c\xf4\x24\x72\xb2\x52\x70\x3b\xd5\x57\xb1\xbf\x48\xf8\xcf\xa9\ -\x69\x8e\x5f\x58\x1d\x47\x20\x50\x9f\xba\x01\x2a\x4e\x75\xfc\x47\ -\x69\x95\xaf\x0d\xf8\xb9\xd5\xb1\x98\x25\x36\x0c\x2e\x19\xa3\xf3\ -\x7e\x8d\xe0\x78\x63\xf0\x3e\x8d\x78\xa2\x63\x99\xb7\x78\x39\xe3\ -\xf2\xa7\x58\x1d\x8a\x12\x00\xa2\x3d\x11\xa4\x27\x25\x90\x9e\x1a\ -\x4f\x46\x4a\x02\x69\xc9\xf1\x64\xa5\x25\x92\x9d\x91\xac\x06\x7a\ -\xe3\xfd\x62\x6a\x9a\xe3\xc7\x56\x07\x11\x28\x82\xf7\x5b\x7d\x88\ -\x76\x55\xf9\xfe\x55\x40\xd0\x65\x97\xe5\x4d\x82\x77\x6b\x02\xe3\ -\xb2\xa1\xb2\x23\x07\x79\xf2\xaf\xc3\x9f\xfd\x73\x38\x9c\x94\xcc\ -\x9a\xcf\x45\x73\x2f\xc3\xe5\x72\xfb\x21\xb2\xc0\xa1\xeb\x3a\x4d\ -\xa7\x4e\x72\xaa\xbe\x96\xc6\xfa\x3a\x4e\xd5\xd7\xd2\x74\xea\x24\ -\x2d\xcd\x8d\xb4\xb7\xb5\xa0\xeb\x01\xf0\x41\x30\x48\xb4\x27\x82\ -\xa4\x51\xb1\x24\xc6\x47\x93\x9c\x10\x47\xe2\xa8\x18\x12\xe3\x63\ -\x49\x4b\x1a\x45\x46\x6a\x02\x91\xe1\x61\x56\x87\x18\x9c\x24\xff\ -\xaf\x38\xdd\xf1\x13\xab\xc3\x08\x24\x2a\x1d\x1d\xa4\xa9\x69\x8e\ -\x5f\x96\x56\xfa\x5a\x10\xfc\x8e\x11\x78\x3a\xe0\x5c\x32\x63\x24\ -\x31\x61\x92\x77\xca\xb4\xa0\xd8\x20\x98\x93\x9b\xcf\x82\x25\x2b\ -\x89\x1d\x95\x60\x75\x28\x96\xd0\x34\x8d\xb8\x84\x24\xe2\x12\x3e\ -\x7d\x3d\x86\x94\x92\xf6\xb6\x16\x5a\x9b\x9b\x68\x6b\x6d\xa6\xb5\ -\xa5\x91\xd6\xe6\x66\xda\xdb\x5a\xe8\xee\xec\xa0\xab\xb3\x83\xce\ -\xae\x0e\xba\xbb\x3a\xe9\xea\xe8\xc0\xe7\xb3\xf7\xd1\x12\x21\x34\ -\x42\xc3\xc3\x08\x0d\x0d\x27\x34\x2c\x9c\xf0\xf0\x48\xc2\x23\xa3\ -\x88\x88\x8c\x26\x35\x3e\x82\x29\xd9\x51\xc4\xc5\xf4\xfd\x2f\x29\ -\x3e\x96\x10\xb7\xcb\xea\x90\x95\xb3\x49\x21\xb8\xa3\x28\xcd\xf1\ -\x1b\xab\x03\x09\x34\x2a\x01\x18\x82\xe2\x74\xc7\x1f\x77\x57\x7b\ -\x5b\xa5\x14\x0f\x13\x44\xff\x0d\x3d\x21\x30\x6f\xb4\xce\x3b\x65\ -\x1a\xcd\x5d\x56\x47\x63\x8c\xe8\xd8\x51\xcc\x5f\xbc\x82\xb1\xe3\ -\x27\x59\x1d\x8a\x6d\x09\x21\x88\x8c\x8a\x26\x32\x6a\x60\x25\x65\ -\x7b\x7b\x7a\xe8\xea\xea\xa0\xbb\xb3\x83\xce\xce\x0e\xba\xba\x3a\ -\xe9\xed\xee\xc2\xeb\xed\xa5\xa7\xbb\x1b\x5d\xf7\xd1\xd5\xd9\x89\ -\xae\xfb\xe8\xe9\x3e\xbd\xe1\x44\xd2\xdd\xd5\xf9\x71\x1b\xba\xcf\ -\x87\x4f\xd7\x71\xb9\xce\x1e\x7c\xdd\xa1\x61\x88\x33\x26\x32\x1d\ -\x4e\x07\x2e\xd7\x47\xf5\xbb\x04\x84\x86\x86\xe1\x74\xba\x71\xb9\ -\x5c\xb8\x43\xc3\x70\xb9\xfb\xfe\x1c\x12\x1a\x4e\x68\x68\x18\xa1\ -\x61\xe1\xe7\xdc\xcc\x29\x80\x39\xa3\x75\xe2\xd4\x03\xbd\x9d\xf9\ -\x24\xf2\x96\xe2\x54\xe7\x43\x56\x07\x12\x88\x82\x66\xf0\xf2\xb7\ -\xa2\x54\xe7\xa3\xbb\x2a\xbd\xbd\x42\x13\x7f\x43\x12\x34\x8f\x04\ -\xa1\x4e\x98\x93\xad\xb3\xad\x42\x70\xb2\x7d\xe4\xac\x20\x39\x1c\ -\x0e\x26\x97\xcc\x66\xf6\xa2\x2b\x70\xbb\x47\x4c\x01\x48\x5b\x70\ -\xb9\xdd\xb8\xdc\x6e\xa2\x3c\x31\x56\x87\x32\x28\x29\x1e\xa9\x06\ -\x7f\x7b\xeb\x41\xca\xeb\xa7\xa6\x3b\x9f\xb2\x3a\x90\x40\xa5\x12\ -\x80\x61\x98\x9a\xee\x7c\x62\x77\xa5\xb7\x5d\x0a\xf1\x24\x10\x34\ -\x5f\x15\x4e\x07\x5c\x9c\x25\xd9\x5e\x01\x27\x5a\x03\x3f\x09\x18\ -\x93\x57\xc0\xfc\xa5\xab\x88\x89\x55\x85\x55\x94\x3e\x1a\x90\x9f\ -\xa4\xce\xc0\xda\x58\xb7\xf8\xff\xed\xdd\x6d\x70\x5c\xd5\x79\x07\ -\xf0\xff\x73\xee\xae\x24\xdb\xb2\x5e\x2d\x59\xda\xd5\xe2\x37\x6c\ -\x6c\x64\x90\x57\xf2\x2b\xd8\x99\x18\x28\xae\x71\x9a\x74\x02\x69\ -\x9b\x69\x93\x69\x87\xbc\x4d\x69\x53\x32\xd3\x4e\x68\x3f\x04\xdb\ -\x9d\x29\xc3\x4c\x98\x14\xe7\xa5\x81\xa4\x69\x52\xd3\x21\x75\x20\ -\x0d\x31\x34\x38\x09\x38\x30\x40\xec\xc1\x92\x4d\x8c\x0c\x06\x1b\ -\xc7\xb5\x91\x8d\x85\x25\x59\x96\xb4\xd2\xee\xde\xf3\xf4\x83\x70\ -\x6b\xc2\x9b\x25\xed\xee\xd9\x7b\xf7\xff\x9b\xf1\x17\x8d\xb4\xfa\ -\x5b\x7b\xf6\x9c\xe7\x9e\x7b\xee\x39\xd0\x3f\x4e\xb6\x44\x1e\x71\ -\x1d\x24\xc8\x58\x00\x4c\x51\xb2\x25\xf2\x58\xd7\x29\xdd\xa8\xd6\ -\xee\x14\x60\xa6\xeb\x3c\x85\x62\x64\xfc\x6c\x81\x67\x8f\x09\xfa\ -\x52\x1f\xfc\xfd\xc5\xa8\xa6\x6e\x16\xd6\x6f\xfc\x38\x16\x2c\x6a\ -\x75\x1d\x85\x8a\x4c\x73\x95\xa2\xb2\xb4\xd6\x7d\x06\xc9\x30\x60\ -\xff\x30\x19\x8f\xfe\xd2\x75\x90\xa0\x63\x01\x90\x03\xed\xcd\xf2\ -\xd4\xfe\x53\x7a\xbd\x5a\xfb\x38\x80\x92\x39\x60\xdb\x13\x60\x45\ -\xc2\xe2\xc9\x23\x06\x99\x00\x2d\x0a\x8f\x44\xa3\x58\xb9\xf6\x7a\ -\xac\x5c\x7b\x03\x22\x91\x92\xb9\x7b\x43\x13\x30\xbb\x64\x4a\xf9\ -\xc0\x19\x10\x98\x4d\xc9\xb8\xf7\x9c\xeb\x20\x61\xc0\x02\x20\x47\ -\x92\xcd\xf2\x7c\x67\x8f\xde\x28\x6a\x1f\x03\xf0\xce\xa5\xd3\x21\ -\x35\x2d\x0a\x2c\xa8\x1f\xdf\x39\x30\x08\x16\x2c\x6a\xc5\x75\x9b\ -\x6e\x41\x75\x4d\xc9\xd4\x69\x34\x09\x65\xec\x19\x8b\xd1\x69\x03\ -\x73\xd3\xb2\xb8\xec\x77\x1d\x24\x2c\xd8\xcc\x73\xa8\x23\x26\x9d\ -\x2f\x9c\xd6\x35\xbe\x6f\x1f\x03\xb0\xc4\x75\x9e\x42\x99\x5b\xa7\ -\x38\xfc\x66\x71\x6f\x1b\x5c\x5b\xdf\x80\xeb\x36\x7e\x1c\xf3\x16\ -\x5e\xe9\x3a\x0a\x05\x40\xd6\x77\x9d\x80\x7e\x47\x37\xc4\x6c\x5a\ -\x16\x93\xe3\xae\x83\x84\x09\x0b\x80\x1c\x6b\x6b\x92\x63\xbf\x39\ -\xae\xd7\x66\xa2\xfa\xb0\xa8\xae\x77\x9d\xa7\x10\x2a\x22\xe3\xc7\ -\x0c\x9f\x1d\x76\x9d\xe4\x9d\xa2\xd1\x32\xac\x58\x7b\x1d\x56\xad\ -\xfb\x3d\x78\x1e\x9b\x3b\x5d\x9a\xa1\xb4\xeb\x04\xf4\xff\xe4\x49\ -\x49\xcb\xcd\xc9\x79\x32\xe0\x3a\x49\xd8\xb0\x47\xcc\x83\xab\xe7\ -\x48\x7f\x77\xb7\xfe\xfe\x58\xad\x7e\x07\x8a\x4f\xbb\xce\x53\x08\ -\x0d\xd3\x15\x67\x8b\xec\xb1\xc0\x05\x8b\x5a\x71\xfd\xa6\x5b\x50\ -\xc5\xe9\x7e\x9a\xa0\x61\x16\x00\x45\x41\x81\xef\x57\x0c\xc8\xe7\ -\x5b\x5b\x85\xef\x48\x1e\x14\x57\x8f\x1d\x32\xaa\x2a\x5d\x3d\xf6\ -\x4e\x01\xbe\x82\x90\xff\xad\x7b\x87\x05\xcf\xfe\xb6\x38\xfe\x8b\ -\x6f\x9e\x39\x85\x91\xe1\xf3\xb8\x6c\xde\x22\xd7\x51\x28\xa0\xe2\ -\x55\x8a\x15\x89\x22\xbe\xa7\x15\x7e\xaa\xc0\xd6\xf6\x98\xd9\x22\ -\x22\x7c\x23\xf2\x84\x33\x00\x79\xf4\x56\xc3\xdd\xbc\xff\x64\xf6\ -\x98\x8a\xdc\x0f\x20\xb4\x0f\x16\xd5\x4f\x57\x18\x11\xd8\x22\xf8\ -\xa8\xce\x6a\x6c\x06\xd0\xec\x3a\x06\x05\x58\x75\xc9\xec\xea\x51\ -\x94\xd2\x10\xfd\x4c\x47\x2c\xb2\xdd\x75\x90\xb0\x2b\x99\xbd\xec\ -\x5d\x4a\xb6\x44\x7e\xa0\x6a\x37\x02\x08\xed\x3d\x2c\x23\x40\xf5\ -\xbb\xef\xa8\x4a\x14\x28\x22\x40\xac\xaa\x08\x2a\xd9\xd2\xd4\x2f\ -\xd6\x6e\x68\xe7\xe0\x5f\x10\x2c\x00\x0a\xa4\xa3\x25\xfa\x24\x8c\ -\xb9\x16\xc0\x6f\x5d\x67\xc9\x97\xea\x0a\x76\x9a\x14\x7c\x31\x6e\ -\x02\xe4\xca\x31\x15\x73\x6d\x32\x11\xfd\x95\xeb\x20\xa5\x82\x05\ -\x40\x01\xb5\x37\xcb\xa1\x6c\xd4\xac\x01\x74\x8f\xeb\x2c\xf9\x50\ -\xc5\x19\x00\x0a\x38\x03\x60\x49\x03\x0b\xd9\xc2\xd3\xe7\x3c\xcf\ -\xac\xee\x88\xc9\x4b\xae\x93\x94\x12\x16\x00\x05\xb6\xb2\x51\x4e\ -\x9f\x8b\x79\xeb\x00\xdc\xed\x3a\x4b\xae\x55\x97\xb3\xe3\xa4\x60\ -\x9b\x57\xaf\xa8\xe4\x59\x50\x05\x25\x8a\xfb\xcb\x07\xbc\xf5\x6d\ -\x4d\x72\xc6\x75\x96\x52\xc3\x45\x80\x0e\xac\x17\xc9\x02\xb8\xa3\ -\xb3\x27\xfb\xa2\xa8\xdc\x07\x60\xba\xeb\x4c\xb9\x50\x35\x6d\xfc\ -\x51\x07\x96\x01\x14\x44\x65\x1e\x70\x05\xaf\xfe\x0b\x69\x14\xd0\ -\xdb\x92\x2d\x91\xef\xb9\x0e\x52\xaa\x38\x03\xe0\x50\x47\x2c\xf2\ -\x80\xa8\xb9\x16\xc0\x31\xd7\x59\x72\x21\x6a\xc6\xb7\x06\x26\x0a\ -\xa2\x25\xb3\x15\x65\x9e\xeb\x14\x25\xe3\x7f\xc4\x98\x0f\xb5\xc7\ -\x39\xf8\xbb\xc4\x02\xc0\xb1\x64\x8b\x1c\xc8\xa8\x59\x01\xc8\x2e\ -\xd7\x59\x72\xa1\x8a\x0b\x01\x29\x80\x66\x94\x01\x73\x6b\xd8\x76\ -\x0b\x42\xe5\x67\xe5\xd6\x24\x93\xcd\xf2\xbc\xeb\x28\xa5\x8e\x05\ -\x40\x11\x58\xd5\x22\x67\x93\x31\xd9\x08\xe0\x0e\x00\x01\x3a\x57\ -\xef\x9d\xf8\x28\x20\x05\xd1\xa2\x59\x0a\x29\x8e\x7d\xac\xc2\x4c\ -\x01\xdc\x9d\x8c\xcb\x47\x5a\x13\xd2\xe7\x3a\x0c\xb1\x00\x28\x1a\ -\x22\xa2\xed\x71\xef\x6e\x55\xfd\x18\x02\xbc\x5f\xc0\x74\x3e\x3e\ -\x45\x01\x33\x2d\x02\x24\x6a\x79\xf5\x9f\x67\x83\x2a\x7a\x73\x7b\ -\xdc\xbb\x43\x44\x02\x7d\x91\x13\x26\x2c\x00\x8a\x4c\x47\x4b\xe4\ -\x51\xb1\x66\xa5\x2a\x5e\x74\x9d\x65\x32\x2a\xa3\xec\x48\x29\x58\ -\xe6\xd6\x29\x3b\xc2\xfc\x7a\x59\xc5\xac\xee\x88\x45\xfe\xcb\x75\ -\x10\x7a\x3b\xb6\xfb\x22\x94\x4c\xc8\xab\x15\x65\x66\x0d\x80\xc0\ -\x2d\x90\xe1\x39\xea\x14\x24\x46\x80\xb9\xbc\xfa\xcf\x1b\x05\xbe\ -\xe3\x79\x66\x39\x9f\xef\x2f\x4e\x2c\x00\x8a\x54\x6b\xa3\x0c\xb5\ -\xc7\xbd\x5b\x55\xf4\x66\x00\x67\x5d\xe7\xb9\x54\x5c\x45\x4d\x41\ -\x12\xaf\x56\x94\xb3\x68\xcd\x87\x01\x40\x3f\xd9\x11\xf7\x3e\xd7\ -\xd6\x24\x45\x78\x50\x38\x01\x2c\x00\x8a\x5e\x47\x2c\xf2\xe3\x6c\ -\xd4\x2c\x05\xe4\x71\xd7\x59\x2e\x05\x17\x52\x51\x90\xcc\xaf\xe3\ -\xd5\x7f\xee\xc9\x13\xc6\x9a\xa5\xed\xf1\xc8\x0f\x5d\x27\xa1\xf7\ -\xc7\x02\x20\x00\x56\x36\xca\xe9\x64\x4c\x6e\x12\xc1\xed\x00\xc6\ -\x5c\xe7\x21\x0a\x83\xba\xe9\x40\x2d\x4f\xfd\xcb\x1d\x41\x46\x81\ -\x2d\xc9\x98\xdc\xb8\x2c\x21\xaf\xbb\x8e\x43\x1f\x8c\x05\x40\x40\ -\x88\x88\x26\x63\xde\xbd\x9e\x67\x96\x43\x70\xd0\x75\x1e\xa2\xa0\ -\x5b\x50\xcf\xab\xff\x1c\x7a\xc9\xa8\x59\xd5\x11\xf7\x36\x73\x95\ -\x7f\x70\xb0\x00\x08\x98\xb6\x26\x79\x71\xd4\x37\xab\x00\x6c\x03\ -\x77\xdd\x25\x9a\x94\xda\x69\x3c\xf2\x37\x67\x14\xdb\x3d\xcf\xac\ -\x58\x16\x97\xfd\xae\xa3\xd0\xc4\xf0\x8e\x6d\x80\x75\xf6\x64\x36\ -\x88\x9a\xef\x03\x68\x72\x9d\xe5\x82\xd1\x2c\xf0\xf8\x61\xd6\x95\ -\x54\xbc\x0c\x80\x75\xf3\x2d\xa7\xff\xa7\xae\x57\xa1\xb7\x76\xc4\ -\x23\x3b\x5d\x07\xa1\xc9\x61\x4f\x1d\x60\x1d\xb1\xe8\x2e\x9b\x31\ -\xed\x80\xfc\xd4\x75\x96\x0b\x7c\x5e\x54\x51\x91\x5b\xda\xac\x1c\ -\xfc\xa7\x4a\xe5\xe1\x68\xc4\x5c\xc5\xc1\x3f\xd8\x38\x03\x10\x12\ -\xfb\x4f\x66\x3f\xa1\x22\xdf\x02\x30\xcb\x65\x8e\xc1\x51\xe0\xc9\ -\xa3\xac\x2b\xa9\xf8\x08\xc6\x4f\xfb\x5b\xdc\xc8\x2a\x75\x0a\x4e\ -\x43\xf5\xaf\xdb\x5b\x22\x0f\xb9\x0e\x42\x53\xc7\x9e\x3a\x24\x92\ -\x2d\x91\x1f\x79\x9e\x69\x85\x62\xbb\xcb\x1c\x9c\x01\xa0\x62\x34\ -\x2d\x02\x2c\x4f\x70\xf0\x9f\x02\x85\x62\x7b\xb9\x35\xad\x1c\xfc\ -\xc3\x83\x33\x00\x21\xd4\x75\x32\x7b\x0b\x44\xbe\x0e\x07\x6b\x03\ -\xfa\x53\xc0\x53\xaf\xb1\xae\x24\xf7\x44\x80\xda\x0a\xa0\xa5\x46\ -\x31\xb7\x56\x61\xd8\xdb\x4d\x8a\x02\x87\x45\xcc\xe7\xda\x63\xf2\ -\xb4\xeb\x2c\x94\x5b\xfc\x48\x84\xd4\xbe\xa3\x5a\x6d\x2a\xec\x56\ -\x00\xb7\x01\x28\xd8\xfe\x7c\xe7\x52\xc0\x6e\x16\x00\x94\x67\x82\ -\xf1\x83\xa7\x2a\xa2\x40\x85\x37\xbe\x9b\xdf\x85\x7f\x15\x11\xa0\ -\x22\xa2\x98\x59\x01\x78\xec\xe1\xa6\x22\x0b\xe0\x9e\x73\x69\xb3\ -\x79\xfd\x3c\x19\x75\x1d\x86\x72\x8f\x1f\x8f\x90\xdb\xd7\xa3\xed\ -\x46\xed\xfd\x00\x3a\x0a\xf1\xfb\xce\x8d\x02\xbb\xb9\x06\x80\xf2\ -\xc0\x08\x90\xa8\x51\x34\x57\x01\xf5\xd3\x14\x51\x6e\x3b\x9d\x37\ -\x0a\xec\x87\x98\xcf\x76\xc4\xa4\xd3\x75\x16\xca\x1f\xf6\xd4\x21\ -\xb7\x3c\x26\x5d\xf6\x94\x59\x03\xe0\x0e\x00\x23\xf9\xfe\x7d\xdc\ -\x0a\x98\x72\x4d\x64\xfc\xc4\xbe\x0d\x8b\x2c\x92\x31\x45\x53\x25\ -\x07\xff\x3c\x1a\x56\xc1\x97\x8e\xc6\xcc\x0a\x0e\xfe\xe1\xc7\xee\ -\xba\x84\x1c\x38\xa1\x71\x2b\xf6\x2e\x08\x3e\x95\xaf\xdf\x31\x92\ -\x01\x7e\xfe\x0a\xeb\x4a\xca\x8d\xa6\x4a\xc5\x95\xb3\x15\x55\x15\ -\xae\x93\x84\x9e\x42\xe4\x21\x40\xfe\xae\x3d\x26\xc7\x5d\x87\xa1\ -\xc2\x60\x01\x50\x82\x0e\x9c\xcc\xac\xf7\x61\xb6\x89\x60\x69\xae\ -\x5f\xdb\x02\xd8\x79\xc8\x40\xb9\xd8\x9a\xa6\xa0\x7e\x06\xb0\xb8\ -\x41\xd1\x30\x83\x0d\x29\xff\x74\x9f\xc0\xbb\x3d\x19\x97\x67\x5d\ -\x27\xa1\xc2\x62\x01\x50\xa2\x76\xab\x46\x6a\x4e\xd9\xdb\x14\xd8\ -\x02\x45\x75\x2e\x5f\x7b\xd7\x2b\x06\xa9\x4c\x2e\x5f\x91\x4a\x81\ -\x00\x98\x5d\xa9\x58\xd4\xa0\xa8\x9b\xee\x3a\x4d\x49\xe8\x51\xc1\ -\x96\xf6\x66\xf3\x5d\xee\xdf\x5f\x9a\x58\x00\x94\xb8\x83\x6f\xe8\ -\xec\x6c\xc6\x6e\x55\xc1\xad\xc8\xd1\xd3\x02\xcf\x1d\x17\x9c\x19\ -\x62\xd3\xa2\x4b\x23\x18\xdf\x97\x7f\x51\x83\xa2\x9a\x53\xfd\x85\ -\x30\x2a\x82\x7b\x8c\x31\x77\xb5\x35\xc9\xb0\xeb\x30\xe4\x0e\x7b\ -\x69\x02\x00\xec\xeb\xd1\xc5\x06\xba\x15\xaa\x9f\x98\xea\x6b\x1d\ -\x3c\x2d\x38\x7a\x96\x4d\x8b\x3e\x58\xe3\x5b\xf7\xf8\x6b\x38\xf0\ -\x17\x84\x40\x1e\x35\x9e\x7c\xb1\xad\x49\x8e\xb9\xce\x42\xee\xb1\ -\x97\xa6\xb7\xe9\x7a\x3d\x73\x03\x60\xbe\x0a\xa0\x6d\xb2\xaf\x71\ -\xbc\x5f\xb0\xbf\x87\x4d\x8b\xde\xdb\xf4\x32\xa0\xad\x49\x31\x7b\ -\x26\xef\xf1\x17\x82\x02\xfb\x45\xcc\xed\xdc\xcc\x87\x2e\xc6\x5e\ -\x9a\xde\x61\x87\xaa\xb7\xa0\xc7\xff\xb4\x40\xee\x04\x30\x67\xa2\ -\x3f\xdf\x37\x02\x3c\x7d\x8c\x4f\x02\xd0\x3b\x09\x80\xf9\xf5\x8a\ -\x2b\x1b\x15\x1e\x9b\x48\x21\x1c\x83\xea\xe6\x64\xdc\x7b\x80\xf7\ -\xf9\xe9\x77\xb1\x00\xa0\xf7\xd4\xdd\xad\x65\xa3\xb5\xf6\xcf\x45\ -\xb1\x05\x13\xd8\x56\x38\xed\x03\xff\xfd\x32\x7b\x77\x7a\xbb\x32\ -\x0f\x68\x8f\x2b\x9a\x78\xd5\x5f\x08\x6f\x02\xf8\xea\xb9\xb4\xb9\ -\x97\xbb\xf8\xd1\x7b\x61\x01\x40\x1f\xe8\x85\xd3\x3a\xc3\xf7\xed\ -\x5f\x61\x7c\x33\xa1\x9a\x4b\xf9\x99\x9d\x87\x0c\x0f\x06\xa2\xff\ -\x53\x11\x01\xae\x99\x6b\x51\x55\xee\x3a\x49\xb8\x29\x70\x5e\x80\ -\x6f\xa5\x2b\xcc\x3f\xad\xae\x97\x41\xd7\x79\xa8\xb8\xb1\x00\xa0\ -\x4b\xf6\x4c\xaf\xce\x9c\x9e\xb6\x7f\x89\x4b\x28\x04\x76\x1d\x36\ -\x48\x65\x0b\x93\x8b\x8a\x5b\x45\x04\x58\x3b\xcf\xa2\xb2\xcc\x75\ -\x92\xf0\xba\x30\xf0\x47\xb2\xe6\xee\xab\xe7\x48\xbf\xeb\x3c\x14\ -\x0c\x2c\x00\x68\xc2\x2e\x2a\x04\xbe\x0c\xa0\xf6\xdd\xbe\xe7\xf1\ -\xc3\x06\xa3\x2c\x00\x4a\x9e\x08\xb0\xe6\x32\x45\x63\x25\xa7\x83\ -\xf2\x81\x03\x3f\x4d\x05\x0b\x00\x9a\xb4\xfd\xc7\xb4\x06\x65\xf6\ -\x6f\x14\xf8\x22\x80\xba\x0b\x5f\x3f\x33\x24\x78\xee\x38\x9b\x16\ -\x01\x0b\xea\x15\x57\x35\x71\xf0\xcf\x83\xb3\x50\xdc\x6b\xc7\xcc\ -\xb6\xe5\x0b\xe4\x9c\xeb\x30\x14\x4c\xec\xa5\x69\xca\x76\x1f\xd3\ -\x8a\xea\xa8\xff\x47\x2a\xf2\x0f\x02\x5c\xf1\x9b\x53\x82\xd7\xfa\ -\xd8\xb4\x4a\x9d\x27\xc0\x8d\x8b\x2c\xca\x23\xae\x93\x84\xca\x69\ -\x05\xee\xd3\x51\xf3\x35\x0e\xfc\x34\x55\xec\xa5\x29\x67\x54\xd5\ -\x74\xf5\xf8\x9b\x0e\x9d\x31\x5f\x7f\xb5\x57\x26\xfc\xf8\x20\x85\ -\x4b\x53\xa5\x62\xf5\x1c\x5e\xfd\xe7\xc8\x11\x11\x7c\xa3\x72\xd8\ -\x7c\x7b\xe1\x42\x19\x73\x1d\x86\xc2\x81\x05\x00\xe5\xc5\xbf\x77\ -\xe9\xad\x83\x29\xbd\xb3\x77\x44\x12\x3c\x18\xa8\x34\x2d\x6e\x54\ -\x2c\x6e\xe0\x9b\x3f\x15\x2a\xb2\x1b\x6a\xbf\xd6\x1e\xf3\x1e\x15\ -\x11\xfe\x31\x29\xa7\x58\x00\x50\x5e\xfd\xdb\x41\xfd\x70\x6a\x08\ -\xff\xdc\x3b\xa4\x6d\xbe\xb2\xb9\x95\x92\xe5\x2d\x8a\x96\x6a\x8e\ -\x59\x93\x90\x86\xc8\x23\xd6\x97\x7b\x96\x27\x64\xaf\xeb\x30\x14\ -\x5e\xec\x91\xa9\x20\x76\x74\xeb\xe5\x83\x43\xf8\xe6\x9b\x23\xb8\ -\x7e\x24\x93\x9b\x43\x87\xa8\xb8\xad\x4c\x28\x62\x55\x2c\x00\x26\ -\xe0\x0d\x05\xbe\x5d\x16\x31\xff\x72\xd5\x6c\x79\xc3\x75\x18\x0a\ -\x3f\x16\x00\x54\x50\xbb\x77\x6b\xc5\x89\x99\xf6\x2b\x7d\xa3\xf2\ -\xd9\xfe\x94\xcc\xe2\xed\x81\xf0\xea\x88\x2b\x12\x35\x7c\x83\x2f\ -\x41\xa7\x0a\xee\x1f\xf3\xcd\xf6\x6b\x12\x92\x72\x1d\x86\x4a\x07\ -\x0b\x00\x72\xe6\x27\x2f\xe8\xba\x33\x29\xbd\xeb\xcc\xb0\xac\x49\ -\xfb\xe0\xde\xc1\x21\xb3\xa4\x51\x71\x05\xd7\x00\xbc\x3b\xc1\x39\ -\xb1\xf8\x4f\x5f\xcd\x37\x96\x27\xe4\xa0\xeb\x38\x54\x9a\x58\x00\ -\x90\x73\x3b\xba\xb5\x2e\x95\xb2\xff\x38\x30\x2a\x9f\xec\x4b\x49\ -\x2d\x67\x05\xc2\x21\x51\xad\xe8\x68\xe1\x9b\x79\x11\x85\xea\xd3\ -\x00\xbe\x67\x8d\xf7\xd0\xf2\x98\x8c\xb8\x0e\x44\xa5\x8d\x05\x00\ -\x15\x95\x07\xbb\x74\xf9\xf9\x2c\xb6\xf6\x8f\xe0\x86\xe1\x34\xa2\ -\xae\xf3\xd0\xe4\x55\x96\x01\x37\x2c\xe4\x01\x74\x00\x5e\x07\xf0\ -\x80\x5a\xf3\xdd\x8e\x84\x1c\x71\x1d\x86\xe8\x02\x16\x00\x54\x94\ -\x76\xa8\x7a\x63\x9d\xf6\x4b\xe7\xd2\xf2\x85\xfe\x94\xcc\xcf\xf8\ -\x6c\xab\x41\x23\x00\x6e\x5a\x62\x11\x2d\xc1\x9b\x3b\x0a\x9c\x17\ -\xc5\x4f\x44\xec\xf6\x65\xb1\xc8\x13\x3c\x8a\x97\x8a\x11\x3b\x55\ -\x2a\x7a\xf7\xed\xd3\xea\xa8\xb5\x7f\x3b\x92\x35\x9f\xea\x1b\xc1\ -\x9c\x2c\xbb\xd2\xc0\x58\x37\xd7\xa2\x7e\x86\xeb\x14\x05\xe3\x03\ -\xb2\x1b\x6a\xb7\x97\x97\x79\x3f\x6e\x6d\x94\x21\xd7\x81\x88\xde\ -\x0f\x0b\x00\x0a\x94\x07\xf7\xeb\xdc\x54\xc6\x6e\x1e\x4c\x9b\x8f\ -\x0c\x8c\xa0\x9e\xb5\x40\x71\xbb\xba\x59\x31\xbf\x2e\xd4\xeb\x00\ -\xb2\x80\xfc\x4a\xc4\xee\x28\xf3\xbd\x87\x5b\x13\xd2\xe7\x3a\x10\ -\xd1\xa5\x62\x01\x40\x81\xf5\x1f\xcf\xeb\xfc\x51\xb5\x5f\x1e\x4e\ -\x9b\x8f\xf6\xa7\xd0\xe4\x87\x7a\x9c\x09\xa6\x39\xb5\x8a\x64\x2c\ -\x74\x6f\x8c\x0f\xe8\x1e\x11\xf9\x51\xc4\x33\x3f\xe4\x33\xfb\x14\ -\x54\x2c\x00\x28\x14\x1e\xec\xd2\xd8\x98\xda\xbf\x1f\x1a\x35\x1f\ -\xeb\x1f\x45\x0b\xd7\x0c\x14\x87\xc6\x4a\xc5\x35\xe1\x38\x0f\x60\ -\x10\x22\xbb\x14\xf6\xa7\x9a\xf2\x76\xf2\x20\x1e\x0a\x03\x76\x92\ -\x14\x3a\xdb\x5e\xd5\xf2\xca\x7e\x7c\x7e\x2c\xab\x7f\x7a\x2e\x8d\ -\x65\x23\x69\x29\x73\x9d\xa9\x54\xd5\x4d\x03\x3e\x34\x3f\x98\x37\ -\x6a\x44\xf1\x1a\x80\x5f\x5a\xd1\x47\x2b\x06\xbc\x5d\xad\xad\x92\ -\x76\x9d\x89\x28\x97\x58\x00\x50\xe8\x3d\x70\x40\x6f\x1c\x19\xd3\ -\x2f\x0c\xa7\x65\xed\xe0\x98\x36\xf8\x96\xcd\xbe\x50\x66\xcd\x00\ -\xd6\xce\x0d\x4c\x01\x30\xa4\x22\x4f\x19\xe8\x2f\xc4\x9a\x9f\x2d\ -\x6b\x91\x57\x5c\x07\x22\xca\x27\xf6\x84\x54\x52\x76\xee\xd3\xe9\ -\xc3\x11\xfb\x67\xc3\x19\xf3\x27\xe7\xc7\xd0\x31\x90\x42\x95\x0d\ -\xc5\x0c\x75\x71\x8a\x57\x29\x56\x24\x8a\xf6\x0f\xec\x43\xb5\x4b\ -\x8c\xfc\x5c\xc5\xfc\xa2\xbc\x0f\xbf\xe6\x55\x3e\x95\x12\x16\x00\ -\x54\xd2\xfe\xb5\x4b\x63\x9a\xb5\x9f\x19\xf3\x65\xe3\x48\x5a\xae\ -\x1c\x4a\xa3\x8a\x3b\x11\xe6\xce\x8a\x84\x22\x5e\x3c\x07\x02\xf9\ -\x00\x0e\x00\x78\x56\x54\x9f\x29\x53\xef\x09\xae\xda\xa7\x52\xc6\ -\x02\x80\xe8\x22\x8f\x1c\xd4\xd9\xe7\xc6\xec\x5f\xa4\x7c\xb3\x71\ -\x38\x8d\xa5\x43\x63\xa8\xcd\x5a\x7e\x4e\x26\x23\x56\xa5\x58\xe9\ -\xf6\xea\x7f\x00\x90\xbd\x0a\xdd\x23\xb0\xcf\x78\x5e\xe4\xd7\x6d\ -\x4d\x32\xec\x32\x10\x51\x31\x61\xc7\x46\xf4\x3e\x76\x74\x6b\x59\ -\x6a\x14\x9b\xc6\x7c\xfb\xd1\x74\x46\x56\xa7\xb2\x32\x67\x28\x8d\ -\x69\x9c\x25\x78\x7f\x65\x1e\x70\xdd\xe5\x16\x15\x91\x02\xfd\x42\ -\x41\x06\x8a\x6e\x28\xf6\x0a\x74\x8f\x35\xde\xde\xf6\x66\xbc\x2c\ -\x22\x7c\xa7\x88\xde\x03\x0b\x00\xa2\x09\xda\xb6\x47\xab\x66\x18\ -\xfc\x41\xd6\xda\x0d\x59\x6b\x92\xa9\x2c\xe6\x8c\xa4\x51\xc9\x99\ -\x82\x71\x02\x60\xd5\x1c\x45\x53\x65\xde\xc6\xde\x21\x00\x87\xa1\ -\x38\x24\x06\x9d\x3e\x4c\x67\xda\x47\x27\x8f\xd2\x25\x9a\x18\x76\ -\x58\x44\x39\xb0\x79\xb3\x9a\xcb\x6e\xc2\xea\x2c\xec\x86\x6c\x56\ -\x56\xa5\xad\x2c\x4c\x67\x31\x3b\x95\xd1\x19\xbe\x96\xd6\xc7\x6c\ -\xe1\x2c\x45\xeb\xec\x9c\x0c\xfe\x63\x00\x8e\x42\xa4\x5b\x55\x0f\ -\x19\xd5\x6e\xf1\xbc\x43\x6d\x4d\x78\x89\x7b\xeb\x13\x4d\x5d\x69\ -\xf5\x4c\x44\x05\xb6\x63\x87\x7a\xa9\xcb\xb1\xd2\xfa\x76\x6d\x06\ -\x26\x99\xce\xe0\x8a\x6c\x56\x63\x63\x56\x6a\xc6\x7c\x94\x67\x43\ -\xb6\x61\x51\xed\x34\x60\xdd\x7c\x8b\x09\x9c\xff\x33\x08\xe0\x35\ -\x88\x1c\x81\xea\x11\x11\x3d\x0a\x5f\x8f\x18\x89\x1c\xb9\x3a\x8e\ -\xd7\x39\x85\x4f\x94\x3f\xa1\xea\x7c\x88\x82\xe6\x07\x7b\x75\x91\ -\x6f\xd0\x61\xad\x6d\xf3\x55\x16\x66\x14\x2d\xea\x4b\x43\x56\x51\ -\x93\xce\x62\xfa\x98\x8f\xb2\xa0\xdc\x5a\x10\x01\x3e\x3c\xcf\xa2\ -\x7a\x1a\x00\xc0\x02\xe8\x55\x45\xaf\x88\xbc\x01\xd5\x1e\x05\x4e\ -\x08\x70\x42\xa1\x27\x55\xbd\xe3\x5e\x16\x27\x92\xf3\x64\xc0\x6d\ -\x6a\xa2\xd2\x15\x88\x8e\x85\xa8\x94\x6d\xee\xd6\xca\xc4\x18\x16\ -\x4b\x16\x0b\x44\xec\x65\x6a\x4d\xb3\x2f\x68\xb0\x16\xf5\x59\xd5\ -\x3a\xa8\x54\x66\xad\xce\x54\x48\xd4\x2a\xca\xad\x22\x6a\x15\x9e\ -\x6f\x35\xaa\x16\xc6\x42\xde\x76\x41\x9e\xb1\x90\x8b\x17\x31\x46\ -\x0c\x60\x0c\x14\x6f\x7d\x2d\xe2\xa9\x42\x45\x01\x58\x23\xb0\x62\ -\xd4\x37\x40\xd6\x33\x92\x8e\x18\x8c\x1a\xd1\x51\x4f\x64\xd0\x18\ -\x1d\x14\xc1\x80\x81\xf6\x89\x67\x7a\x23\x6a\x4f\x2e\x69\x34\x4f\ -\xd9\x72\xf4\xae\x68\xc0\x19\x4e\xd3\x13\x15\xb7\xff\x05\x82\x22\ -\xaa\x2f\x56\xbc\x39\x1a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -\x00\x00\x07\xda\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ -\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x07\x57\x49\x44\ -\x41\x54\x58\x85\x9d\x97\x6b\x50\x54\xe7\x19\xc7\x7f\xef\xd9\x0b\ -\xec\x72\x0f\x77\x64\xb9\x09\x2a\xa0\x46\x44\xb4\x01\x43\x84\x18\ -\x31\xd5\x54\x21\xd3\x66\xd2\xcc\x34\x56\x33\xd3\x69\xcd\x4c\x6a\ -\x9c\xd6\x74\x3a\xcd\x74\xfc\xd0\xd4\xde\x92\x69\xda\xa9\xe9\x34\ -\x37\xe3\x2d\x35\xa3\x24\xc6\x4b\xea\x2d\x4a\x0c\x04\x04\x2d\xa0\ -\x48\x50\x50\x58\x40\x60\x57\xb9\x2e\xcb\x5e\xce\xdb\x0f\xe8\xba\ -\xcb\x6e\x84\xf4\xf9\xf4\x9e\xe7\xfd\x3f\xcf\xff\x7f\xce\x79\xce\ -\xf3\x3e\x47\x30\x43\xfb\xb8\x49\xc6\x0f\x3b\xd4\xad\x23\x0e\x65\ -\x8d\xdd\x29\x53\x9c\x6e\x61\xb0\xbb\xd1\x48\x09\xb3\x22\xa4\x3d\ -\x3f\x49\x6d\x92\x42\x5c\x12\xc8\xd3\x36\xbd\xe6\xc8\xf2\x58\x31\ -\x32\x93\xbc\x62\x3a\xc0\xbe\x26\xb9\xc2\x3a\xca\x5b\xd6\x51\xe6\ -\xb8\xe5\xa4\x4f\xaf\x01\x83\x0e\x0c\x3a\x89\x51\x37\xb9\xce\x8a\ -\x91\xde\x61\x36\x60\x9f\x50\x95\x1d\x79\x26\xd1\xf6\x7f\x09\xf8\ -\xb2\x4b\x1a\x5a\x7a\xe5\x71\xf3\x90\x28\x76\x4b\xdf\x3d\x8d\x80\ -\xd2\x4c\x15\xa3\x0e\xae\xf4\x0b\x86\xec\x50\x98\x2a\xfd\x93\x08\ -\x9c\x52\xf2\xc6\xb0\x43\x79\xb5\x24\x5d\xd8\x67\x2c\xe0\x40\x93\ -\x2c\x1c\x9b\x90\x95\x42\x10\xdb\x3f\x2a\x18\x99\xb8\xbf\x17\x16\ -\x04\x2e\x15\xe6\xc4\x4a\x12\xc3\x24\xc1\x5a\xff\x78\x55\x95\x28\ -\x8a\x77\x6a\x59\xa3\x3a\x35\x15\x4b\xd2\x44\xef\xb4\x02\x76\xd7\ -\xcb\x67\x1c\x2a\xef\x76\x0d\x61\x88\x36\x4a\xcc\x7d\x23\xdc\xea\ -\xed\x62\xc0\x62\x21\x42\x19\x21\x44\xa7\x62\x34\x04\x93\x9e\x1c\ -\x4f\xf1\xb2\x05\x3e\xb1\x36\x9b\x9d\x77\x0e\x7c\xc6\xac\x84\x18\ -\xca\xcb\x8a\xa6\xa6\x36\x4b\x55\x59\x93\x6f\x12\x8d\xde\x4e\x1f\ -\xfd\x7b\xea\xe4\xca\x1b\x83\xec\x1d\x19\x9f\x50\x9a\x2f\xd6\xd0\ -\x78\xa1\x1a\x4b\xbf\x9f\x68\x00\x72\x32\x53\x3d\x02\xa4\x94\x9c\ -\xa8\x6a\xe0\xef\xbb\x3e\xc1\x3a\x38\xcc\x4b\x1b\xcb\x03\x85\x24\ -\x0b\x45\x3d\x72\xa4\x5d\x2e\x5f\x93\x21\x6e\xfa\x09\x78\xeb\x82\ -\x8c\x30\x8f\xca\xc3\xad\x5f\xb7\x2a\x47\x0f\xee\xc6\x36\x3a\xa3\ -\x22\x06\x60\xfb\x5f\x77\x73\xa2\xaa\x61\x26\xd0\xe4\x20\x97\x7a\ -\xec\xcb\x2e\x99\x5f\x68\x12\xe3\x3e\x02\x54\x87\x3c\x59\x75\xae\ -\x2a\xf8\xf4\xf1\x83\x20\x03\x14\xd4\x03\xac\xbb\xd7\x3a\x63\x6c\ -\x98\x81\xec\xd6\x01\xb9\x0f\x58\x0f\xa0\x00\x7c\x78\x49\x96\xd4\ -\x5d\xbe\xbe\xe4\xcc\x14\x72\x45\x11\xac\x2c\xca\x63\xfb\xd6\x0d\ -\x3c\xb3\xf6\xb1\x6f\x25\x2a\x90\xb9\x25\x7c\x7e\x5d\xa1\xdd\x2a\ -\xd6\xfd\xab\x4e\x2e\x85\xbb\x4f\x20\x54\xe7\xde\x72\xfe\x3f\x87\ -\x90\x5e\xe4\xc6\xe0\x20\x5e\xdb\xb6\x89\xfc\x05\x59\x00\x58\xef\ -\x0c\xcd\x88\x24\x3b\x33\x85\xa5\x0b\xe7\x06\xdc\x33\x0f\x0a\xc6\ -\x1c\x93\x6b\xbb\x4b\xbe\x0b\xe4\x6a\x6b\xac\x32\xfc\x7a\x73\xc7\ -\x13\x5d\x66\xb3\x0f\xf8\x95\xcd\xcf\x7a\xc8\x67\x62\x11\xe1\x21\ -\xfc\xe4\x87\x6b\x59\x5b\xba\x0c\x97\xea\xa6\xa1\xb9\x8d\xb6\x1b\ -\x3d\x58\x07\x87\x09\x35\x18\x50\x43\xe2\x08\x49\xc8\x41\xa7\xd3\ -\x03\x70\xc7\x26\xb2\xf7\x36\xc9\x78\xad\xde\xee\x5e\xd3\xd0\x7c\ -\x2d\xd8\x3b\xd9\xdc\xd9\x26\x4a\x1f\x79\x78\xc6\xe4\xab\x4b\x0a\ -\x58\x59\x94\x47\x70\xb0\x9e\xbd\x1f\x9f\x66\xdf\xe1\x33\x0c\x0d\ -\x8f\xf9\xe1\x8c\xa1\x61\xac\x5e\xf7\x2c\x19\x73\x72\x71\xba\x11\ -\xb6\x09\xf5\x57\x0a\x88\xd2\x76\x73\x9f\x0f\xf0\xd1\x82\xf9\xd3\ -\x92\xce\x9f\x9b\xea\x59\x57\x94\x15\x71\xe5\xeb\x9b\x3c\xbf\xe5\ -\x0f\xec\xdc\xf3\x69\x40\x72\x00\xdb\xe8\x08\x95\xfb\xdf\xc6\xdc\ -\x79\x1d\x00\xa7\x53\x79\x5c\xdb\x37\xc2\xf2\x6e\xcb\xb8\x0f\x30\ -\x29\x2e\xfa\x1b\x89\x33\x52\x92\xd8\xb2\xa9\x9c\xbc\xdc\x4c\x00\ -\x7a\xfa\xac\xbc\xf9\x5e\x25\x55\x75\xcd\xd3\x8a\x06\x50\xdd\x6e\ -\x4e\x1e\x3e\xc0\x86\x9f\x6d\xc3\xe6\x24\x4d\x7b\xb9\x5f\xc9\x9c\ -\x70\xf9\x36\x44\xad\xce\xbf\xbf\x86\x87\x18\xf9\xf9\xc6\x0a\xca\ -\xcb\x8a\xd0\x68\x14\x26\x1c\x4e\xf6\x54\x9e\x62\x4f\xe5\x69\x26\ -\x1c\x4e\x0f\x4e\xa7\xd5\xf0\xc4\xa3\xf9\xac\x5e\x51\x80\x29\x31\ -\x96\x7e\xeb\x20\x7f\xd9\x75\x92\xd6\x96\xfb\x02\x2d\xfd\xbd\xf4\ -\x76\x77\x62\x48\x4f\x35\x68\x8b\xd2\xa4\x7a\x2c\xd2\x80\xf7\x91\ -\x75\xab\xdf\xff\xbb\x2e\x7b\x6c\x89\x67\x7d\xae\xb6\x99\xbf\xbd\ -\x5f\x49\x4f\x9f\x2f\x6e\x59\xde\x3c\x5e\xfa\x71\x39\x29\x49\x71\ -\x1e\x5f\x74\x54\x04\x6b\x7f\xb0\x09\xeb\xce\x3f\x62\xe9\xeb\xf1\ -\xf8\xcd\x37\xae\x91\x9c\x92\xaa\xd1\x06\x69\x24\x0b\x33\x62\xa9\ -\xae\xb9\x9f\xe8\xf8\xd9\x7a\x2a\xbe\xbb\x82\x6b\x56\x0d\x63\x4e\ -\x88\x0c\x86\xb8\x50\xc9\xcd\x9e\x01\x3e\xd8\x77\x90\xaf\x2e\x5d\ -\xf5\x21\x4e\x8a\x8f\xe6\xc5\xe7\xd7\x53\xbc\xd4\xbf\x76\x14\x01\ -\x39\x09\x82\xdc\x85\x4b\x38\x7b\xe2\x13\x8f\xdf\x6a\xe9\x07\x26\ -\xfb\xc0\x70\xde\xfc\xcc\x18\xef\xa0\xf6\xce\x1e\x5e\xdc\xfe\x0e\ -\x8b\x8b\x56\x11\x16\x1e\x8e\xd5\xd2\x4f\x4b\x63\x3d\x57\x9b\xea\ -\x71\xbb\xdd\x1e\x5c\x90\x5e\xc7\x73\xeb\x4b\x79\x6e\xfd\xe3\x04\ -\xe9\x75\xb8\xdd\x2a\x87\x3e\x3b\x4f\x58\x88\xc1\xe7\x89\xcd\x8d\ -\x95\xa4\x25\x45\x71\xd6\x8b\xc3\x31\x31\x8e\x5e\x83\x5b\x8b\x94\ -\x1d\xb9\x59\xa9\x31\xe9\xa6\x04\x3a\xba\x6e\x79\x00\x57\x5b\x2e\ -\x73\xb5\xe5\xb2\xdf\x1d\xdd\xb3\x45\x0b\x17\xb0\x75\xd3\x3a\xd2\ -\x67\x4d\x16\xec\xc5\xcb\xd7\x78\xfd\xed\x43\xb4\x77\xf6\x04\x3c\ -\x8c\x92\xc3\x5d\x3e\xd7\x1a\x54\x52\x22\xa5\x59\x3b\x39\x46\x51\ -\xf0\xf2\x0b\x4f\xb3\x65\xfb\x4e\x5c\x5e\x77\x18\xc8\xa2\xa2\x63\ -\x29\x7d\xb2\x82\xf4\xac\x1c\xae\xd9\x20\x4d\xaa\xbc\xf9\xde\x21\ -\xfe\x7d\xe4\xdc\x03\xe3\x7a\xfb\x2c\x3e\xd7\x19\x09\x46\xb2\x63\ -\xe5\x71\x45\x20\x4f\x03\xe4\xe5\x66\xf2\xbb\x6d\x1b\x79\x28\x32\ -\x3c\x60\x02\x83\x31\x94\xe2\x95\x4f\xb1\x61\xf3\x2b\x24\x26\xa7\ -\x71\xa9\xee\x0b\xc6\x1c\xd0\x71\x47\xd0\xdc\x7a\x33\x60\x8c\xb7\ -\x55\xd5\xfa\x7e\xa6\x69\xc9\xf1\x08\xe4\x29\x6d\x90\x4e\xf3\xe9\ -\x84\x53\x1d\x03\x42\x0a\x17\xe7\xf0\xcf\x3f\xfd\x9a\x5d\x27\x5a\ -\x19\xb4\x0e\x60\x9f\x18\x27\x24\x34\x9c\xb8\x84\x59\xcc\x32\xa5\ -\x23\x14\x85\xa6\x86\x1a\xaa\x4e\x1d\x26\x32\x32\x86\x45\x05\xcb\ -\x69\xec\x15\x0c\x06\x1c\xb6\xee\xdb\xc9\xf3\x17\x69\xbb\xd1\xed\ -\xe3\xcb\x5f\x90\x35\xae\xd7\x6b\x8e\x69\x73\xe3\xc4\x68\x43\xb7\ -\x7b\x3f\xb0\x09\x40\x55\x82\x98\x93\xe3\xdf\x86\x6f\x0f\xf4\x71\ -\xf4\xd0\x6e\x6e\x75\x77\x02\x10\x19\x79\xbf\x6e\xa7\x9e\xde\xa3\ -\x5e\x23\x5c\xed\x7f\x5b\xd9\xf1\x8f\xfd\x3e\xfb\x39\x99\xa9\x64\ -\xa5\x27\xef\xce\x8d\x13\xa3\x5a\x00\xa1\x2a\x3b\xa4\x46\xfd\x11\ -\x12\x5d\x7c\xa8\x24\xca\x20\xb8\xe3\xdb\x1c\xb9\xd1\x7e\xd5\x43\ -\x3e\x9d\x35\xb6\xdf\xe6\xe4\xf9\x8b\x9c\xa9\xbe\xc4\xb9\xaf\x9a\ -\x7c\x4e\x59\x45\x51\xd8\xbc\xe1\x7b\x4e\x55\xab\xfc\x1e\xee\x1e\ -\xc7\x79\x26\xd1\x56\xdf\xed\x7e\x43\xc0\x2f\x34\x0a\x2c\x4a\x54\ -\xf9\xbc\x43\xf9\xb6\x73\x89\xc7\xea\xaa\xcf\x52\x57\xed\xef\x57\ -\x14\xc1\xcb\x2f\x3c\xcd\xc3\xf3\x32\xfe\xbc\x38\x5e\xb4\xc3\xdd\ -\x81\x04\x60\xd8\xa1\xbc\x0a\xb2\x06\x20\xc2\x00\x69\x91\x0f\x66\ -\x8f\x88\x8e\x79\xe0\xfe\x54\x8b\x8e\x0c\xe7\xf5\xdf\xfc\x94\xf5\ -\xab\x1e\xa9\x0e\xb3\x29\xbf\xbd\xe7\xf7\x34\xfd\x92\x74\x61\xaf\ -\xed\x97\xe5\x5a\xa7\x5a\x0b\x98\xe6\xc5\x49\x86\xec\x82\xdb\x53\ -\x5e\x45\x48\x68\x38\x2b\xca\xd6\x91\xbd\x20\x7f\x5a\x52\x21\x04\ -\xb3\x53\x92\x78\x6a\xe5\x77\x78\xb2\xa4\x00\x63\x70\x50\x8f\xa2\ -\x2a\xdf\xcf\xca\x12\x9e\x2a\xf1\x1b\xcb\xeb\xbb\xe4\x42\xa1\xa8\ -\x47\x80\x64\x09\xf4\x0c\x09\xea\xcd\x82\x86\xda\x2f\x18\xbc\x63\ -\xa1\x70\xc5\x6a\xf4\x41\x3e\xe3\x03\x6d\x2d\x8d\xdc\xb6\xf4\x31\ -\x61\xb7\x13\x1f\xae\x65\x76\x82\x91\xa4\x84\x68\xb2\x67\x9b\x88\ -\x8a\x08\xbb\x07\xeb\x52\x55\x65\xcd\x12\x93\x68\xf2\x11\x19\x48\ -\x79\x43\xaf\x8c\x45\x75\x7f\x04\xa2\x18\xe0\x5c\x87\x82\x75\x4c\ -\x22\xc4\xb4\x7f\x72\x14\xa7\xab\x3c\x64\x9c\xea\x95\xd5\x2e\x9d\ -\xa6\x62\x69\x9c\xb8\x35\x75\x47\x99\xea\x00\x58\x9c\x28\x06\xc2\ -\x6c\x9a\x55\xc0\x76\x60\x4c\xaf\x99\x9e\x3c\x3a\x04\x16\x25\xca\ -\xa9\xe4\x0e\x24\xaf\x85\xd9\x34\x25\x81\xc8\x61\x06\x3f\xa7\xb5\ -\xfd\x32\xe1\x4a\xa7\xfc\xa8\x73\x50\x14\xba\xd4\xc0\xf8\xb4\x28\ -\xc9\xa2\x24\x9f\xa2\x1d\x13\x92\x3d\x2e\x9d\xb2\xa3\xe0\x6e\xb5\ -\x7f\x93\x4d\xff\x4c\xef\xda\x07\x17\x64\xa2\x4b\xaa\xbf\xb4\x39\ -\x45\x99\xdd\x25\x4c\xe3\x4e\x8c\x6e\x15\x45\x05\xe6\x27\xc8\x91\ -\xcc\x68\xf5\x8a\x44\x5c\x94\xaa\x3c\x13\xac\xd7\x1c\xcd\x8d\x13\ -\xa3\x33\xc9\xfb\x3f\xd5\x8a\xed\x13\x27\x02\xa0\xfb\x00\x00\x00\ -\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x10\xb6\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ -\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x10\x33\x49\x44\ -\x41\x54\x78\x9c\xcd\x9b\x79\x7c\x55\x45\x96\xc7\xbf\x75\xef\xcd\ -\xcb\xbe\xef\x8f\x40\xd8\x95\x1d\x42\x02\x88\x0e\x04\x11\x05\x45\ -\x11\x5a\x47\xe4\x23\xbb\x1f\x46\xdb\x71\x9b\xd6\x19\x9d\xed\xe3\ -\xa7\xfb\xd3\xfd\x69\x5b\xc7\xb5\x6d\x07\x51\x36\x31\x2a\x22\x88\ -\x8d\xf4\x20\x9b\x18\x10\x24\x31\x21\x40\x80\x84\x26\x10\x42\xf6\ -\x7d\x7b\xc9\x5b\xee\xad\xf9\x23\x24\x64\x79\xb9\x79\x59\xb0\xfb\ -\xf7\xd7\xab\x3a\x55\xa7\x4e\x9d\x5b\x75\xea\xd4\x39\xf5\x04\x37\ -\x19\x9b\x4f\xc9\x71\x86\xcb\x58\xed\x70\x29\x49\x0e\x9d\xa1\x76\ -\x9d\x08\xa7\x0b\x8b\x21\x51\x9d\x06\x42\x00\x9a\x82\xf4\x52\x71\ -\xcd\x1c\x22\x73\xfc\xbd\xe5\x19\xe0\x02\x42\x66\xab\x8a\x9a\x3a\ -\x29\x46\x94\xdd\x4c\xf9\xc4\x80\x73\x94\x52\x6c\xce\x60\x55\x93\ -\x93\xa7\x6a\x9b\x18\x6f\x73\xe2\xed\x69\xd7\xd1\x91\x92\xb1\x51\ -\xb2\x23\x37\x49\xb6\x50\xf8\x16\x94\x4f\x13\xac\x22\x7d\xa0\xc5\ -\x1d\x30\x05\x6c\xc8\x92\xc3\xa4\x9d\x77\x6a\x6c\xcc\x6b\x70\x78\ -\x3e\x69\x21\x20\x36\x50\x52\xd1\x28\x98\x33\xd2\xc0\x57\x33\x6d\ -\x7e\x4e\xc0\xa6\x46\x8b\xb2\xfe\x8e\x48\x51\xdf\x6f\xa1\x19\x00\ -\x05\x6c\x3d\x2e\xc7\xd4\x49\x36\x55\xd9\x98\xe6\x32\xcc\xf9\xc5\ -\x04\x4a\xa2\x03\xa1\xb0\x16\x62\x03\x41\x15\x10\xe6\x27\x29\xaa\ -\x13\x54\x35\x41\x62\x9c\xc4\xa2\x7a\x34\x6c\xb5\x84\x77\xbc\x5c\ -\xca\xdb\x13\xe3\x45\x75\x7f\xe4\xef\xb3\x02\x5e\xc9\x96\x96\xe8\ -\x3a\x63\x4b\xb9\x4d\x79\xc4\xa5\x7b\xc6\xc7\xd7\x0b\x46\x86\x4b\ -\xe2\x82\x25\x42\x80\x45\x85\x9c\x72\xc1\xf9\xb2\x96\xee\xe1\xfe\ -\xf0\x0f\x43\x8d\xde\x88\x51\x25\x04\xbf\x9e\x1c\xab\xbc\x2b\x84\ -\xe8\x55\xc7\x56\xf4\x49\x01\x3f\x15\xc8\x91\xb9\x55\xc6\xbe\xf3\ -\xa5\xca\xf0\xde\xf4\xb3\xa8\x70\xf7\x68\x03\x4d\x69\x29\x4b\xa0\ -\xbc\x41\xd0\xec\x82\xba\x66\xa8\x77\xc0\x6d\x43\xa4\x29\x0f\xf7\ -\x90\xc7\x0c\x5d\x5d\x99\x38\x44\x5c\xea\x6d\xcf\x5e\x2b\x60\xc7\ -\x59\xfd\xf9\x9a\x26\xf1\xfb\xaa\x46\x61\x89\x0d\x92\x54\x37\x41\ -\x45\xa3\x67\x6c\x84\x80\xd1\x11\x12\x45\x80\x97\x0a\xc3\xc3\xfa\ -\x32\x59\xf7\x90\x50\x2f\x90\xeb\x12\x06\x69\x9f\xf5\xa6\x9f\xb9\ -\xc9\x69\x87\xed\x52\xaa\xae\x2c\xf6\xf8\x6a\x72\x7e\x4e\xbd\x20\ -\xd2\x5f\x32\x2c\x54\xe2\xd4\x05\x15\x8d\xdd\xf7\xf3\xb7\xc0\x90\ -\x10\x49\x49\xbd\x40\x08\xd0\xd4\x16\xa3\x17\x60\xf1\x5c\x48\xbb\ -\xc3\x89\xb7\xc5\xcb\xb4\x8d\x80\x40\x10\x9f\x66\x14\xe9\x49\x53\ -\x62\x95\x17\x3d\xdd\x12\x1e\x7d\xba\x6d\x27\x64\x50\xb5\x2e\x4f\ -\x4f\x88\x95\xf1\x27\xae\x2a\xd8\x1c\x10\xe0\x0d\x0a\x50\x67\x87\ -\x48\x7f\x89\xa6\x42\x71\xdd\x0d\x76\x01\x16\x48\x18\x64\x10\xe6\ -\x07\xf5\x8d\x36\x6a\x9a\x20\xdc\x4f\xc1\xcf\xcf\xc7\x93\x21\x01\ -\xb8\x5c\x50\xc2\x9b\x1f\x7e\xc9\xb2\x45\x77\x32\x23\x61\x8c\xc7\ -\xfd\x80\x14\xa3\x58\x59\x95\x98\x28\x9c\x3d\x35\xec\x71\x05\xfc\ -\x39\x5d\xfa\xe5\xd9\xc9\x6d\xb4\x8b\xe8\x8b\x15\x60\x73\x80\x97\ -\x02\xf1\x21\x92\x06\x07\xd4\x3b\x04\xe5\x8d\x82\xaa\xf2\x52\xf2\ -\x2f\xe7\x52\x51\x56\x4c\x6d\x55\x25\xcd\x0d\x95\x54\x56\x56\xe1\ -\x74\xe9\x5d\x78\xfa\xf9\x78\x13\x15\x19\xca\xff\xfc\xc7\x3a\xa2\ -\x23\x42\xbb\xd0\x6d\xb6\x66\x36\x7e\xb1\x8f\x1d\x7b\x53\x71\xe9\ -\x3a\xcb\x16\xdd\xe9\xe9\xc4\x5b\xb1\x4c\x58\x65\x70\x7a\xba\x5c\ -\xdc\x93\x12\x4c\x15\xf0\x8a\x94\x4a\xfe\x0f\x9c\xae\x6c\x24\x1a\ -\xa0\xa0\xa6\xe5\x0b\x3b\x0d\x38\x53\x2c\xc9\xcb\x3d\x47\x4e\x76\ -\x26\x57\xf3\x72\x69\x6c\xa8\xf3\x58\x3a\x5b\xb3\x9d\x2b\x05\x25\ -\xd8\xed\x8e\x0e\xf5\x52\x4a\xf6\xa7\x66\xf0\xde\xd6\xaf\xa9\xac\ -\xf1\x9c\x9f\x3b\x08\x29\xef\x13\xb1\x72\x8b\x94\xf2\x31\xb3\xed\ -\x60\xaa\x80\xe8\x1f\x49\x2b\x6e\x64\x44\xfb\x3a\x97\xd3\x49\x56\ -\xfa\x31\xd2\x8e\x1d\xa2\xa1\xbe\xb6\x5f\x42\xb6\xc7\xa5\xfc\x22\ -\xde\xf8\x70\x27\x59\xe7\x7b\x6d\xc8\xcd\xf0\xe8\xe1\x4b\x46\x3d\ -\xf0\x4f\xdd\x35\xe8\x56\x01\x1b\xd3\xe4\x1b\x57\xaa\x49\x68\x5f\ -\x97\x97\x9b\xcd\x81\x3d\x5f\x50\x57\xdb\x2f\xdf\xa3\x03\xec\x0e\ -\x27\xef\x6f\xfb\x33\xbb\xf6\x1d\x43\xd7\xfb\x74\x94\x9b\xc2\x69\ -\x88\x75\x1f\xa5\x49\xfb\xda\x24\xf1\x8c\x3b\xba\x5b\x05\xa4\xa4\ -\xcb\x3b\xf2\x6a\x79\xae\xb5\x6c\xe8\x3a\x07\xf7\x7e\x49\x56\xfa\ -\xb1\x01\x17\xb0\xb4\xbc\x8a\x1d\x7b\x53\x07\x9c\x6f\x2b\xc2\xfc\ -\x24\x5e\x8a\x5c\x9d\x56\x2a\xdf\x4a\x8a\x16\x79\x9d\xe9\x4a\xe7\ -\x8a\xed\xdb\xa5\x5a\x62\x63\x8f\xe3\xba\x77\xe7\x74\x3a\xf8\x62\ -\xeb\xfb\x37\x65\xf2\x3f\x07\x54\x01\x21\xbe\x04\x94\x57\xcb\x5d\ -\xdb\xb7\xcb\x2e\x8e\x76\x17\x05\xd4\x0e\x35\x36\xd7\x34\x13\x0c\ -\x2d\x5f\xfe\xeb\xcf\x37\x51\x70\xe5\xe2\xcf\x21\xeb\x4d\x43\x95\ -\x0d\x32\x8b\xc4\xc4\xda\x78\xe3\xa3\xce\xb4\x0e\x0a\xd8\x7d\x41\ -\x5a\xcb\x1a\x94\x65\xad\xe5\xd4\x83\xdf\x70\xf9\xe2\x39\x8f\x07\ -\x0a\x09\x0a\x20\x79\xc6\x44\x1e\x5f\xba\x80\x7b\xe7\x4c\xeb\x97\ -\xd0\x03\x85\xf3\x65\x82\xef\x2f\x2b\xd8\x5d\x50\xd2\xa0\x2c\xff\ -\x24\x43\xc6\xb7\xa7\x77\xb0\x01\xe5\x75\x6c\xb5\xbb\x5a\x94\x72\ -\x2d\x3f\x8f\xf4\x1f\x0e\x79\x34\xc8\xd0\xc1\x31\xac\x5c\x32\x8f\ -\x39\x33\x27\xa1\xa9\x2d\xab\xec\x44\xc6\x79\xf6\x1e\x3e\x39\x30\ -\xb3\xe8\x23\x1a\x9d\x90\x5b\x71\xc3\x39\x73\xea\x28\x0d\x4e\xb6\ -\x00\xc9\xad\x75\x6d\x0a\xd8\x7e\x51\x46\xe6\x17\xb5\x10\x34\x05\ -\xd2\xbf\xdb\x8d\x94\xe6\xbe\xba\x10\x82\x47\x16\xce\x66\xdd\xb2\ -\x7b\xb1\x78\x99\xbb\xaa\x7f\x0b\x54\xda\x04\x9d\xa7\x50\xd6\xc0\ -\xac\xf7\x4f\xc9\x41\x4f\x4e\x16\x85\xd0\x4e\x01\xa3\xfc\x8c\x95\ -\x23\x47\xa2\xd6\xdb\x21\x2b\x3b\x87\xbf\xe6\x5d\xe9\x71\x80\xa7\ -\x56\x3c\xc0\xd2\xfb\x93\x07\x54\xe8\x81\x42\x79\xa3\xe0\x6c\x71\ -\x57\x4f\xdf\xa9\x23\x34\x3b\xef\x01\x0f\x42\x3b\x05\x18\x92\x95\ -\x42\x40\xa0\x37\xec\x3f\x72\xa2\xc7\x01\x96\xde\x9f\xfc\xb3\x4c\ -\xde\x4b\x53\x09\x0a\xf2\x73\x4b\xb3\x35\xd9\xa9\x6f\xb4\xa1\xaa\ -\x0a\x81\xfe\x7e\x78\x5b\xbc\x30\x24\xfc\x78\x55\x50\xd6\x20\xe8\ -\x6e\xfd\x56\x36\xb1\xa0\xf5\xb7\x06\x90\x59\x2c\x93\xa4\x61\x8c\ -\x87\x16\xc7\xe4\xf8\x4f\xe7\x4d\x85\x8a\x8a\x08\xe1\xf1\xa5\x0b\ -\x4c\xdb\x0c\x04\xa6\x4d\xba\x85\xe7\xd6\x2e\x61\x88\x35\x0a\x80\ -\xe2\xb2\x2a\x8e\x67\x9e\xe7\x44\xc6\x39\x2e\x5c\x2a\xa0\xaa\xe6\ -\x46\x54\x4c\x51\x04\x71\x31\x91\x0c\x1d\x39\x86\x61\xe3\xa7\x13\ -\x19\x3d\xa8\x5b\xbe\x36\x07\x96\xad\x69\x72\xf9\x8a\x24\xf1\x71\ -\xcb\x0a\x90\xc6\xa3\xad\xc4\xb3\x39\x57\xb0\x35\xdb\x4d\x05\x7b\ -\x62\xd9\x42\x7c\xbc\x7b\x71\x9f\xed\x25\x62\x22\xc3\x78\x7a\xe5\ -\x22\x66\xcf\x98\x08\xc0\x99\x9c\xcb\x6c\xdd\xb1\x9f\x13\xa7\x2e\ -\x74\x6b\x97\x0c\x43\x72\xb5\xa8\x8c\xab\x45\x65\xa4\xa6\x7e\xcf\ -\xb8\xc9\xd3\xb8\x73\xc1\x12\x2c\xde\xee\x6f\x9f\x8d\x3a\xcf\x00\ -\x2d\x0a\x90\x92\xbb\x5b\x09\x17\xaf\x14\x9a\x0a\x17\x1c\xe4\xcf\ -\x9c\x99\x93\xfb\x34\xb1\x9e\x60\xd1\x34\x1e\x5d\x74\x27\xcb\x97\ -\xcc\xc5\xc7\xdb\x42\x79\x55\x2d\x7f\xda\xfa\x35\xfb\x8f\x66\xf4\ -\x8a\x8f\x94\x92\xb3\x99\x3f\x52\x5a\x74\x8d\x87\x56\x3c\x81\x7f\ -\x40\x50\x97\x36\x75\xcd\x8c\x07\xd0\xb2\x4a\x64\x94\xae\x1b\x63\ -\x5b\x09\x97\xf2\x8b\x4c\x99\x4f\x9f\x7c\x2b\x5e\x9a\x67\x91\xcb\ -\xde\x60\x66\xc2\x58\x9e\x59\xb3\x98\xb8\x98\x08\x9c\x2e\x9d\x94\ -\xaf\x0e\xb1\x79\xc7\xb7\x3d\xae\x46\x33\x94\x97\x16\xf2\xf5\x67\ -\x1b\x79\x64\xf5\xd3\x28\x6a\x47\x99\x1b\x1d\xf8\x7c\x98\x26\xc7\ -\x6b\xba\x4b\x9f\x85\x10\x02\x5a\x82\x1b\x7f\x2d\x32\x8f\x36\x8f\ -\x1b\x35\xb4\xcf\x02\xb9\x83\x9f\x9f\x0f\xaf\xbe\xf4\x38\xb7\x27\ -\x8e\x03\xe0\x64\x56\x0e\x6f\x7d\xb4\x93\xab\x45\x03\x93\x0f\x29\ -\x2c\xb8\xcc\x89\xd4\x6f\x99\x99\xdc\xd5\x66\x29\x18\xcb\x35\x29\ -\xc4\x78\x01\x48\x09\x99\x85\x0a\x8d\xb6\x66\x53\x86\x83\x63\x23\ -\x07\x44\xb0\x56\x44\x84\x06\x13\x91\x18\x4c\x49\x79\x15\xef\x6e\ -\xd9\xcd\x91\x13\xa7\x07\x94\x3f\x40\xda\xb1\xc3\x24\x4c\x9f\x8d\ -\x8f\x6f\xc7\xd3\xc4\xe1\x12\xd3\xb4\xbc\x4a\x31\xaf\xa6\x19\xaa\ -\x6d\x82\x06\x07\xd8\x1d\xe6\x0a\x08\x0c\xf0\xed\xb7\x40\x16\x4d\ -\xe3\x91\x07\x92\x89\x89\x0e\xc7\xe1\x74\x92\xb2\xfb\x30\xdb\x76\ -\x1d\xa4\xb9\x53\x80\x64\xa0\xe0\x74\xd8\x39\x75\xf2\x28\x33\x66\ -\xdf\xdd\xa1\xbe\x59\x17\xc3\xb5\x2b\xd5\x4c\xae\xb7\xdf\x70\x18\ -\xbc\x54\xf3\xfd\x6d\xf1\xf2\x38\x8e\xea\x16\xed\xf7\xfa\xb1\xf4\ -\x6c\xde\xde\xb4\x8b\xa2\xd2\x4a\x8f\xfa\x46\x85\x87\x90\x7c\xdb\ -\x24\x26\x8f\x19\x41\x54\x44\x30\x0d\x8d\xcd\x5c\xb8\x54\xc0\xe7\ -\x7b\x8e\x50\x5d\x6b\xbe\x75\x2f\x9e\xcf\xea\xa2\x00\x97\x2e\xc3\ -\xb5\x61\x61\x34\x9d\x29\xc1\xaf\xf5\x74\xb1\x58\xcc\x8f\x37\xbb\ -\xb3\xc7\x38\xa3\x5b\xc4\xc5\x44\xf0\xcc\x9a\xc5\xcc\x4c\x18\xcb\ -\xb5\x92\x0a\x5e\xfc\xed\x07\x1c\xcf\x34\xf7\x37\x5a\x11\x1e\x12\ -\xc4\xe3\x4b\x17\xb0\x60\x4e\x52\xdb\x5d\xa3\x15\x89\x13\x47\x33\ -\x3f\x39\x89\xb5\x2f\xff\x91\xca\x8a\xf2\x6e\x79\x94\x96\x14\x52\ -\x5f\x5b\x43\x60\x70\x48\x5b\x9d\xc3\x10\xde\xda\xf0\x30\x29\xbc\ -\x14\x38\x53\x22\x70\xe8\x20\x34\x73\x05\x54\x55\x37\x78\x24\x74\ -\x2b\x7c\x7d\x2c\xac\x58\x32\x8f\x47\x1e\x48\xc6\xd0\x0d\x3e\xf8\ -\x74\x2f\x9f\xed\x3e\x8c\xc3\xe5\xea\xb1\xaf\xa6\xaa\x3c\x7c\xdf\ -\x2c\x56\x3f\x7c\x0f\x7e\xbe\xdd\xa7\x1b\x7d\xfd\x83\xb8\x67\xf1\ -\x63\xa4\x6c\x78\xb3\x7b\x66\x52\x52\x5c\x98\xdf\x41\x01\xba\x8e\ -\xaa\x01\x01\x83\x43\x5a\xd2\x55\x35\xcd\x90\x1a\x16\x40\xbe\x49\ -\x58\x2e\xef\x6a\x31\x77\x24\x8d\xeb\x51\x78\x21\x04\x77\xdd\x3e\ -\x85\x5f\xae\x78\x80\xa8\xf0\x10\x0e\x9f\xc8\xe2\x8f\x9b\x77\x53\ -\x5a\xe1\x59\x38\x2d\x71\xe2\x68\x9e\x5f\xb3\x84\xf8\xb8\x68\x0c\ -\xc3\x3c\x54\xe6\xe7\x05\x71\x71\x43\x19\x34\x78\x18\x85\x05\x97\ -\xbb\x6d\x57\x55\x5e\xda\xa1\xac\x1b\x88\xb6\x78\x80\x10\x10\xea\ -\x0b\x13\x46\x44\x99\x0e\x76\xe2\x54\xcb\xb2\x35\x68\xb9\x70\x9c\ -\x29\x69\xf1\xbb\x3b\x67\x87\x92\x26\x8d\xe6\x95\xe7\x57\xd0\xd4\ -\x64\xe7\xb9\x5f\xbf\xcf\x7f\xbd\xbe\xd9\xa3\xc9\x47\x47\x84\xf2\ -\x9b\x17\x56\xf1\xd6\x7f\x3f\x89\xaf\xaf\x37\xaf\xbc\xb9\x95\xb4\ -\xac\x5c\xd3\x3e\x42\xc0\xad\xd1\x92\x61\xa3\xcd\x73\x07\x95\x15\ -\xa5\x5d\xea\x34\xa0\x01\x08\x6b\xad\x88\x8f\x8b\x36\x65\x72\xfa\ -\x7c\x1e\xc7\x4f\xe7\x53\x69\x19\x86\x4b\x07\x1f\x0d\xf2\xae\xdb\ -\xb0\xd8\xa0\x96\x4c\x50\x98\x1f\x5c\x2e\x77\x71\xe4\xe0\x3e\xf6\ -\x1d\x38\x82\x4b\xef\x9a\x1b\xe8\x0c\x8b\xa6\xb1\x74\xd1\x1c\x56\ -\x2c\xb9\x0b\x45\x55\xf8\x78\xe7\x01\xb6\xee\xdc\x4f\x53\xb3\x83\ -\xf9\xb3\x93\x7a\xec\x3f\x3a\x42\x92\x78\x4b\x2c\x47\x0f\x76\xdf\ -\xa6\xa9\xb1\xe3\xf6\x55\x15\xa4\x06\xd4\xd3\x4e\x01\x63\x47\x0e\ -\x41\x08\x61\x1a\x0b\x78\xf5\xfd\x14\x1e\x5e\xf5\x34\xfe\x01\x41\ -\x68\x2a\x34\x5f\xdf\xce\x45\x75\x02\x5d\xd7\xc9\x3e\x75\x92\x63\ -\x87\xf6\x7a\x9c\x2b\xb8\x6d\xca\x18\x9e\x5d\xbb\x84\xb8\x98\x08\ -\x7e\xc8\x38\xc7\x3b\x1b\x77\x71\xad\xa4\xc2\xa3\xbe\xed\x31\x66\ -\x70\x80\x29\xdd\xe9\xec\x78\xcc\xaa\x2a\xba\x06\x54\x00\x6d\x61\ -\xa2\xe8\x88\x50\x46\xc6\x5b\x4d\xef\x04\x15\xe5\x65\x6c\x7e\xef\ -\x55\x12\x67\x26\x13\x6d\x1d\x8c\xb7\xb7\x2f\x75\x75\xd5\x14\xe6\ -\x5f\x22\xe7\xec\x29\x8f\x27\x6e\x8d\x0e\xe7\xd9\xd5\x8b\xb9\x3d\ -\x71\x1c\xd7\x4a\x2a\xf8\xd7\xdf\x6d\xe0\x87\x0c\xcf\x43\x70\x9d\ -\xe1\xd3\xc3\x09\xe6\xb0\x77\x74\xab\x2d\x8a\xb4\x6b\x40\x2e\x30\ -\xb5\x3d\x61\xf6\x8c\x89\x3d\x5e\x8a\x9a\x6c\x0d\xa4\x1e\xd8\xd3\ -\x37\x41\xbd\x2d\x3c\xb6\x78\x2e\xcb\x16\xcd\x01\x21\x58\x9f\xf2\ -\x0d\x9f\x7f\xfd\x9d\x47\x27\x83\x19\x7a\x3a\xa2\x0d\xd9\x71\x2b\ -\xfa\x5a\x44\xb1\x26\x20\xa7\xf3\x62\x5f\x72\xcf\xed\x7c\xb2\xfb\ -\x10\x4d\x4d\x7d\xbf\x88\x74\x87\xd1\x63\x27\x71\xf7\x7d\x0f\x92\ -\x38\x32\x14\x2f\x2f\x49\x41\x61\x29\x1f\xef\x3c\x30\x20\xbc\x6b\ -\xeb\x6d\xa6\xf4\x60\x7f\x1f\x62\x02\x24\xde\x5e\x30\x2c\x54\x12\ -\xe2\xc3\x16\x4d\x0a\x79\x16\xd9\xd1\x82\x07\x05\xfa\xb3\x74\x61\ -\x32\x9b\xbe\xd8\x37\x20\x82\x01\x84\x86\x47\x31\xf7\xbe\x5f\x30\ -\x74\xc4\xad\x00\xec\x3e\x72\x96\xa4\x31\x71\x0c\xed\x7a\x53\xed\ -\x33\x0a\x8a\xba\x77\x84\x00\xa2\x42\x7d\x99\x11\x7f\xe3\x73\x4b\ -\x21\xb3\x35\x55\x51\x53\x75\xdd\x90\x74\x4a\x95\xaf\x7c\x68\x1e\ -\x69\x59\x39\x9c\xcd\xbd\xd2\x2f\xa1\x2c\x16\x6f\x6e\x9b\x7d\x0f\ -\x09\xb7\x25\xa3\xaa\x2a\xd5\x55\x15\x1c\xfe\xcb\x4e\xf2\x72\xb3\ -\x89\x7a\xf6\xdf\x19\x64\x6e\xb7\x7a\x85\xec\xdc\xee\x7d\x00\x68\ -\xf1\x28\xdb\xc1\x90\xa8\xa9\xda\xa4\x18\x51\xf6\xd3\x35\x3d\x5b\ -\x88\x96\x00\x41\x2b\x34\x55\xe5\x77\x2f\xae\xe6\x57\xbf\xfd\xa0\ -\x47\x7b\xd0\x1d\xc6\x4c\x98\xca\xec\xbb\x17\x11\x10\x14\x8c\xd3\ -\xe9\xe0\xf8\x77\xfb\x48\x3b\x76\x10\x5d\x6f\xd9\xeb\x4e\xe3\x46\ -\xc6\xb9\xbf\x70\xb8\x5c\x9c\x3c\x65\xee\x2f\x8c\x88\xb7\xb6\xfd\ -\x96\x90\x95\x68\x15\x15\x1a\x80\xa2\xb0\x5f\xca\x8e\x0a\x00\x08\ -\x0b\x0d\xe2\xbd\x5f\xff\x33\xaf\x6f\xfc\x8a\xfd\x47\x4e\xf6\x18\ -\x26\x07\x40\x08\x46\x8c\x1a\xcb\xb4\x59\xf3\x18\x34\x78\x18\x00\ -\xb9\xe7\xb2\xf8\x6e\xdf\x57\xd4\xd5\x54\x75\x69\x5e\x54\x37\x30\ -\x0a\x38\x78\x34\x93\xfa\x46\x73\x1b\x30\x6a\xa8\xb5\x7d\xf1\x00\ -\x5c\x0f\x8a\x4a\x94\x14\x30\x9e\x77\xd7\xc9\xcf\xcf\x87\x07\x7f\ -\xf1\x28\xd6\xb1\xc9\x9c\xc9\xfc\x91\xcb\x17\xcf\x53\x5d\x59\x8e\ -\x94\x37\xdc\xd3\x80\xa0\x60\xa2\x63\x07\x33\x7c\xd4\x58\x86\x8f\ -\x1e\xd7\xe6\x6f\x57\x57\x96\x71\xf0\x9b\x2f\xb9\x72\xe9\x42\xb7\ -\x42\x35\xf7\xcf\xf0\x03\x2d\xd1\xe1\xf5\x9f\x7c\x63\xda\xc6\xdf\ -\xcf\x97\x71\xb7\x0c\x6d\x2b\x2b\x52\x49\x81\xeb\x0a\x48\xb0\x8a\ -\xf4\x9f\xae\xe9\x67\x3b\x6f\x83\x56\xc4\x04\x42\x44\xb4\x95\x39\ -\xf3\x17\x33\x67\xfe\x62\x5c\x2e\x27\x0e\x7b\x33\x86\x6e\xe0\xe3\ -\xeb\x87\xe6\x26\x29\x52\x5c\x98\xcf\x67\x1f\xbd\x8d\xee\x81\x17\ -\xd8\x5f\xfc\xef\x27\x7b\xa8\xa8\x36\x7f\xab\x70\x7b\xe2\x58\x2c\ -\x5a\xdb\x55\xfe\xdc\x94\x38\x71\x0a\xda\xe5\x06\x15\xc1\x96\xee\ -\x3a\x17\x75\xf2\x6b\x34\xcd\x0b\x3f\xff\x40\x02\x82\x82\xdd\x4e\ -\x1e\xa0\xd9\xd6\x38\x60\x93\xaf\x6e\xea\x9e\xb6\x6d\xd7\x41\x76\ -\xfe\xdf\xd1\x1e\x79\x2c\x9c\x3b\xe3\x46\x41\xd2\x96\x24\x6d\x53\ -\x40\xa3\x45\x59\x0f\xb8\xbd\xad\x34\xde\x9c\x40\x8d\xc7\xc8\x29\ -\x17\xa4\x5e\x51\x28\xa8\xbd\x61\x2f\xec\x0e\x27\x6f\x6f\xdc\xc5\ -\xfa\x14\xf3\xa5\x0f\x30\x69\xcc\x08\x12\xc6\x8d\x6c\x2d\x56\xd9\ -\xbc\x95\x0d\xad\x85\x36\x05\x5c\x7f\x7b\xfb\xae\x3b\x06\x13\x62\ -\x25\xbe\x7f\xc3\xd4\x9f\x04\x2a\x1b\x21\xa7\x4c\xe0\x70\xb9\xf8\ -\xcb\x77\x69\xac\x7e\xe1\x75\xbe\xd8\xfb\x7d\x8f\x86\x59\x51\x14\ -\x7e\xb9\x62\x61\x7b\x66\x6f\xb5\x7f\x67\xdc\x21\xbe\xa5\xb9\x94\ -\xb7\x5c\x9a\xf1\x24\xd0\x21\xf2\x19\xe4\x0d\x09\x83\x24\x3f\x5c\ -\xe9\x3e\xdd\x74\x33\x91\x93\x7d\x8a\xa2\x6b\xf9\x54\x96\x14\xf2\ -\xc6\xb5\x4b\xd4\x35\x98\x5b\xfb\xf6\x78\x6c\xf1\xdc\xf6\x91\xec\ -\x52\xc3\xae\xbc\xd3\x9e\xde\x41\x01\x13\xe3\x45\x75\x46\xa1\xeb\ -\x25\x10\x5d\x1e\x12\x44\xfa\x4b\xac\xc1\x50\x58\x3b\x30\xc7\x56\ -\x6f\x70\x36\xf3\xc7\x3e\xf5\xbb\x3d\x71\x1c\x6b\xff\x71\xfe\x8d\ -\x0a\x21\x5f\x4c\x1c\x21\x3a\x58\xcb\x2e\x2f\x44\xa6\x58\xd5\x4d\ -\x20\xdd\xbe\x87\x19\x1f\x2d\x51\xbb\xf4\xf8\xfb\xc4\xb4\xc9\xb7\ -\xf0\x9b\x5f\xad\x44\x6d\x13\x58\x7e\x3f\x25\x56\xdd\xd6\xb9\x5d\ -\x97\xe9\x08\x21\xa4\xa1\xab\x2b\x11\x74\x39\x57\x5a\x5f\x7b\xff\ -\xbd\xe3\xe1\x7b\x67\xf1\xda\xcb\xeb\xda\xbf\x59\xa8\xd1\x35\x75\ -\xb5\x10\xa2\x8b\xf0\x6e\xbf\x67\xe2\x10\x71\x09\x43\x3e\xee\x8e\ -\x76\x4b\xa4\x24\xd0\xe3\xbf\x43\xb8\x87\xa2\x28\x24\xcc\x98\x45\ -\x50\x70\x58\xcf\x8d\x7b\x81\xd8\xa8\x30\xde\xf8\xcf\x27\x78\x76\ -\xcd\xe2\x76\x5f\x1e\xa4\x90\x6b\xdd\xbd\x10\x03\x93\x77\x82\x09\ -\x71\xda\x8e\x8c\x22\xfd\x0d\x24\xff\xd2\xbe\x5e\x11\x30\x2a\x42\ -\x92\x59\xd8\x37\x83\x18\x37\x64\x04\x73\xef\x7b\x88\xc8\x18\x6b\ -\xcf\x8d\x3d\x44\x74\x44\x28\xcb\x16\xdd\xc9\xfd\x77\x4d\xef\xf2\ -\x52\x45\xc0\x1f\x12\xac\xda\xce\xee\xfa\x9a\x66\x39\xa6\xc4\x2a\ -\x2f\x64\x14\x19\x61\x02\x56\xb5\xaf\x1f\x12\x22\xf1\xd1\xe0\x74\ -\x89\xa0\xc1\xc3\x90\x81\x7f\x40\x10\xc9\xf7\x2c\x62\xcc\x84\xa9\ -\x2d\x51\xcc\x7e\x22\x2a\x22\x84\xc4\x09\xa3\x99\x3b\x73\x0a\x49\ -\x93\x46\xa3\x28\x6e\x17\x73\xca\x64\xab\xf2\xb2\x19\x1f\x53\x05\ -\x08\x21\x64\x7a\xba\x5c\x27\xac\x32\x0a\x29\xef\xed\x20\x40\x80\ -\x64\xee\x08\x49\x69\x83\xe0\x74\xb1\xc0\xd6\x4d\x30\x46\x51\x55\ -\x12\xa6\xcf\x62\x66\xf2\x7c\xb7\xb9\x7a\xcd\xcb\x42\x78\x54\x0c\ -\x75\x35\xd5\x38\x1d\x5d\xb5\xa9\xaa\x1a\xb1\x51\x61\xc4\xc5\x86\ -\x63\x8d\x0c\x63\x78\xbc\x95\xa9\x13\x46\xf5\x98\xa3\x14\x88\x3d\ -\x7a\xb1\x58\x25\x06\x99\x3f\x9b\xf7\xe8\x53\x1c\x96\x52\x0b\x2e\ -\x32\xd6\x03\x6b\xdc\xd1\x9b\x5d\x70\xf8\x52\xcb\x53\xb4\x56\x5c\ -\xbe\x78\x8e\x93\x47\x0f\x71\xd7\xc2\x87\x08\x8f\x8c\xf1\x64\x18\ -\x00\x1c\xf6\x66\xa4\x34\x10\x08\x2c\x3e\xbe\xf8\x59\x60\xde\xa8\ -\x1e\xfe\x8c\xd4\x09\x42\xb2\x4d\x2f\x51\xd6\x78\xf2\x5c\xde\x63\ -\xbe\x52\x4a\x91\x51\x64\xfc\x41\xc0\x0b\xee\xe8\xa7\x8a\x05\x57\ -\xaa\x6e\xb0\x73\x39\x9d\xdd\xde\x13\x7a\x83\xd8\x20\xc9\xf4\xc1\ -\x1e\x5b\x1b\x29\xe0\xb5\xc9\x56\xe5\x25\x77\x16\xdf\x1d\x3c\x3e\ -\xd5\x85\x10\x72\xea\x20\xf5\x45\x21\xe4\x62\xdc\xdc\x19\x02\x3b\ -\x05\x64\x07\x62\xf2\xd0\x62\x70\x3d\x44\x9d\x94\x72\xe9\x94\x41\ -\xea\xbf\x79\x3a\x79\xe8\x85\x02\x5a\x31\xc5\xaa\x7d\xa5\x6b\x4a\ -\x22\xc8\x0e\x2f\x9c\x6f\x86\x83\x34\x38\x44\x12\xe6\x51\x36\x5e\ -\x7e\xaf\xaa\xca\xe4\xa9\x71\xda\xf6\xde\x8e\xd1\x67\x73\x2c\xa5\ -\x14\x99\x85\xfa\x72\x84\x78\x0d\x88\xba\x5a\x2b\xc8\xb8\xd6\x7f\ -\xeb\x1e\xee\x0f\xb7\x46\x4a\x42\x7c\x24\x5e\x3d\xbf\xc4\xa9\x92\ -\x82\x97\x13\x62\x95\x0d\xbd\xf9\xea\xed\xd1\x6f\x89\x33\x2f\xcb\ -\x10\x69\x31\x9e\x29\x69\x10\x2f\x9c\xc8\x17\x81\xfd\xe1\x35\xc5\ -\x2a\x89\x0f\xf5\x68\x1e\x95\x48\xde\x36\xec\xca\x3b\x9d\x7d\xfb\ -\xde\x62\xc0\x6e\x36\x9b\x4e\xca\x18\x17\xf2\x4f\xd5\x36\xb1\xa0\ -\xc1\x81\xe7\xff\x8c\xba\x0e\x4d\x85\x85\xb7\x9a\x67\x81\xa5\xe4\ -\x2c\xb0\xd1\xc7\xa2\x6c\x18\x17\x25\x7a\x97\xa7\xef\x06\x37\xe5\ -\x6a\xb7\x35\x4d\x2e\x6f\xd2\x79\xaa\xce\xce\xc4\x06\x3b\xbe\x9e\ -\xae\xcd\xfb\xc7\x1a\xa8\x1d\x25\x32\x80\xd3\x12\xf6\x2b\x52\x49\ -\x69\x0d\x63\x0d\x24\x6e\xfa\xdd\x76\xeb\x4f\x72\x8c\xcb\x30\x56\ -\x39\x75\x91\x60\xd7\x19\xe5\x70\x89\x08\x97\x81\xc5\x65\xa0\xb9\ -\x0c\x84\x04\xbc\x14\x64\xb0\xaf\x6c\x9a\x19\x6f\x9c\x03\x91\x23\ -\x24\x17\xa4\x22\xb3\x25\x6a\x6a\xa2\x55\xf4\x3e\x4b\xda\x0b\xfc\ -\x3f\xcf\xc2\x42\x82\x41\x31\x16\x33\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ -\x00\x00\x03\xb1\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ -\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x03\x2e\x49\x44\ -\x41\x54\x38\x8d\x5d\x93\x5d\x4c\x9b\x75\x14\xc6\x7f\xff\xb7\x6f\ -\x8b\x6d\x0a\x2b\x8c\x41\xd9\x04\x5c\x1d\x96\x16\x27\xac\x2c\xab\ -\xba\x10\x03\xce\x69\x9c\x2e\x12\x8c\x2e\x3a\x13\x89\x0e\x83\x9a\ -\x5d\xe0\xc7\x05\xd1\x99\x18\xbd\x51\xb6\xcc\x45\x13\xa2\x75\xd3\ -\x69\x1c\x31\xce\x68\xc0\x1b\x98\xd3\x18\x97\xe9\x36\x98\xb8\x15\ -\x5a\xfa\xa1\x80\xb4\x0c\x3b\x69\xf9\x18\xb4\xd0\xf7\xef\xc5\xe4\ -\x0d\xf3\x5c\x9d\x9c\xe7\x39\x1f\x4f\xce\x39\x82\xff\xd9\xd1\xf3\ -\xf2\xd1\xf4\x32\xaf\xcd\x66\x70\x0a\x30\x59\x4d\x52\x7a\xcb\x64\ -\x40\x4a\x4e\xa9\xaa\xe2\xab\xb6\x8b\xcb\xab\xf9\x62\xc5\xe9\xbe\ -\x20\x2d\x33\xaa\xec\x09\x27\x44\x7d\x56\x03\xa3\x02\xf7\xdc\xaa\ -\x31\xbb\x28\x28\xc9\x93\x48\x29\x11\x42\x64\x05\x74\x9a\x92\x4a\ -\x5b\x55\x95\xc8\xe8\x05\x8e\x5d\x94\xb6\xf9\x05\x6d\xf8\xdc\x80\ -\xdf\x1e\x1d\x19\x42\xa6\x53\x90\x4d\xf3\x54\xe3\x0e\xb6\xd5\x38\ -\xf9\xe1\x97\x41\xce\x0f\x06\x79\xf5\xb9\xc7\x56\xfa\x9e\x4e\x8d\ -\x88\x5d\xf5\xf5\x62\x51\x05\x98\x9c\x9c\x3e\xf3\x91\xef\xa8\x7d\ -\x32\x36\x76\x83\x9c\xfb\xea\x6a\xe9\xfd\xa9\x9f\x37\x8f\x7c\xce\ -\x9d\x1e\xd7\x2a\x44\x36\x2c\x6f\x90\x3e\x60\xaf\xf2\x8d\x5f\xd6\ -\x75\x75\x9d\x70\x4f\xc6\xc6\x70\x94\xad\xe7\xf0\x81\x56\xb6\x54\ -\x6d\xd2\xa9\x8b\x99\x25\x00\x8a\xd7\xe6\xeb\xb1\x85\x25\x38\x3b\ -\xa6\x3c\xf9\xd9\x45\xf9\x88\x9a\x8c\x47\x9e\x0d\x8d\x04\x31\xdf\ -\x94\x43\x47\xfb\x3e\x8a\x0a\x6d\x74\xf5\xfc\xa8\x93\xd7\xda\x72\ -\x39\xb0\x7f\x2f\xaa\x6a\xe0\xf5\x8e\x4f\xb8\x9a\x9c\xc3\x5c\x58\ -\x86\xb7\xee\x7e\x66\x2d\xe6\x37\xd4\x68\x7c\xe6\x01\x80\x6a\xb7\ -\x83\xa2\x42\x9b\x9e\x78\x47\xa5\x03\xcf\xed\x9b\x98\x9b\xbf\xc6\ -\xa7\x27\xfb\x18\x0a\x8d\xae\x92\x10\x61\x32\x1e\xe3\xe9\x67\x5a\ -\x5d\xea\x5c\xd6\x58\x00\x60\x50\x14\x1d\x6e\xd9\xf3\x20\x45\x85\ -\x36\x3e\xfc\xe2\x3b\x7a\x4e\xff\x8a\xd5\x62\x66\x7f\x73\x23\x3b\ -\xee\xae\xe1\xec\xc8\x55\x3a\xde\xeb\x64\x34\x12\x24\x36\xf5\xb7\ -\x49\xdd\xee\x2a\x48\x77\x82\x3a\x70\x29\xcc\xd7\x3f\xff\x81\x31\ -\x77\x1d\x61\x7f\x84\xde\xde\x5e\xe6\xaf\x2d\xf0\x50\x83\x97\x96\ -\x27\x76\x31\x95\x48\x52\x90\x9f\x47\x83\x27\x8f\xaf\x1c\x15\x84\ -\x02\x7e\x92\xff\x24\x50\x37\xde\x6c\x1f\xad\xdd\x5c\xe1\xee\xbf\ -\x14\xe2\xd0\xe1\x23\xfa\x14\xeb\x4b\xcb\x69\x6d\x6d\x62\x4b\xb9\ -\x99\xf6\x77\x8e\x61\xb1\xe4\xd0\xd1\xde\x82\xd9\x08\x6b\x72\xb2\ -\x00\x6c\xb0\xa4\x13\xaa\x94\xf4\xbd\xfd\x4a\xb3\xfb\xd0\xf1\x53\ -\x0c\x87\xc7\xb1\x58\xad\xdc\xe6\xaa\x46\xd3\xb2\x84\x26\x66\x99\ -\xf8\x6b\x82\xdf\x03\x51\x7d\x8d\x57\x12\xd3\xf8\x83\x51\x00\x9c\ -\xe5\x6b\xba\x55\x55\x55\x7c\x56\x8b\xf9\xc5\xa6\xc6\xdd\x86\x50\ -\xe2\xfa\x61\xf6\x75\x7f\xc9\xe0\x85\x33\xec\xdc\xbd\x87\xf9\xff\ -\x8e\x75\x2a\xb5\xc4\xb9\xdf\x82\xbc\x7f\xfc\x5b\x16\x16\x33\x6c\ -\xab\xae\x94\x9b\x9d\x8e\x83\x4a\xb5\x5d\x5c\x16\xd0\xe9\x5c\x27\ -\x31\xab\xd7\xc7\x9f\x49\x4d\x03\x60\x34\x1a\x75\x49\xd1\x48\x98\ -\xb6\xb7\x3a\x89\x8e\xc5\x71\x57\x94\xf3\xd2\xbe\x26\x9f\xa7\x44\ -\x0c\xa9\x00\xa6\xa4\xd2\x26\x6d\xb2\xd2\x5b\xa6\xdd\x3b\x10\x53\ -\xc8\xcd\xb3\xf1\xf0\xe3\xcd\x38\xdd\x35\xfc\x19\x0e\x50\x7a\x4b\ -\x05\xc5\x36\x13\xa5\xc5\x36\xbc\x35\x2e\xee\xaa\x75\x7f\x2f\xae\ -\x18\x5e\xd0\x7f\x01\xc0\xef\x97\xa6\xb4\x4d\x3b\x38\x9e\x12\xcf\ -\xf7\x8f\xa3\x20\x74\x88\x7c\x33\x6c\x2f\xd7\x50\x0d\x64\x81\x0f\ -\xb4\xb8\xf2\xf2\xd6\xad\x62\xe9\x86\x02\x2b\x76\x32\x20\xeb\xa7\ -\x92\xbc\x3b\x93\xa6\x2a\xb3\x4c\x4e\xae\x09\xad\x6e\xa3\x36\x2c\ -\x14\x7a\x11\xca\xc7\x9e\x12\x31\xb4\x9a\xff\x2f\xc7\xd3\x35\x8d\ -\xdf\xe8\x52\x39\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x0c\x01\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ -\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x0b\x7e\x49\x44\ -\x41\x54\x68\x81\xb5\x9a\x79\x70\xd5\x55\x96\xc7\x3f\xe7\xf7\x7e\ -\x2f\xfb\x1e\x08\x21\x0b\x90\x8d\x80\x49\x34\x31\x09\x8b\x10\xe8\ -\xb8\x01\x5a\xb6\xb4\xd6\xa8\x88\x38\x8e\xd8\x3d\x88\xe5\x14\xf6\ -\xbe\x59\xd3\xce\x54\x69\x6b\xd3\x5a\x25\x53\xad\xe8\xb4\x82\x2c\ -\xda\x2d\x22\x4d\xf7\x40\x43\x77\xc9\xe2\x02\x91\x10\x76\x12\x48\ -\x08\x31\xfb\x9e\x97\x3d\x6f\xfb\xdd\xf9\xe3\x25\x21\x21\xbf\x97\ -\x0d\xfc\xfe\x45\xce\x3d\xf7\x9c\xef\xb9\xef\xfe\xce\xbd\xe7\x5c\ -\x84\x9b\x80\xf7\x2f\xa8\x4c\x7a\x8c\x67\x7a\x5d\xb2\xd8\xee\x96\ -\x38\xbb\x93\x10\x97\x81\xee\x34\x10\x01\x74\x0d\x35\x27\x4a\x35\ -\x25\x44\x70\x5a\x50\x25\xa0\x8e\xbb\xac\x96\x43\xf3\xa2\xa4\xfe\ -\x46\x7d\xcb\x64\x27\x6e\x3d\xad\x66\xf7\xd9\xd5\xab\x9d\x76\xb9\ -\xa7\xb3\x8f\x40\x35\x8a\xae\x06\xa4\x46\x29\x52\xa7\x5e\xa7\xa5\ -\xd4\x09\x44\xb6\x39\x95\xb6\x73\x7e\x9c\xb4\x4c\x86\xc7\x84\x03\ -\xd8\x5a\xa4\x16\x76\xf4\xa9\x77\x5a\xba\x25\xdd\x30\x61\x1d\xe2\ -\x07\x53\x03\x15\xb6\x5e\x21\x22\x40\x11\xee\x0f\x15\x6d\x70\xdb\ -\x74\x45\xa0\x8f\x57\xb3\xdd\xa2\x78\x57\x94\xb6\x31\x33\x5e\x6a\ -\xbe\x95\x00\xfe\xfc\x95\xf2\x6f\xd6\xd4\x9e\xc6\x4e\xb9\xd7\x8c\ -\xf8\x00\x74\x0d\xe6\xcf\x50\x04\xfb\x2a\x7c\x75\x38\x51\x25\xd4\ -\x76\x08\xa1\x7e\x90\x9f\x64\x8c\xe5\xa6\x47\xc1\xef\x42\x7a\xb4\ -\x57\x52\x52\xc4\x7e\xd3\x02\xd8\x71\x5a\x2d\x33\x0c\xb5\xa5\xd2\ -\x26\xd1\x76\xd7\xe8\xba\xba\x06\x77\x26\x1b\xf4\x38\x60\x4a\xa0\ -\x47\x66\x77\x41\x6b\x8f\x30\x3d\x64\xb4\x8d\x36\x0c\xa7\x35\xa5\ -\x3d\x9a\x19\x27\x97\xc7\x52\xd4\xc6\x52\xf8\xf8\xbc\xfb\xa7\xfe\ -\x16\xe3\x2f\x55\x36\x89\xbe\x33\xc9\x20\x3e\xd4\x9c\x44\x90\x0f\ -\x4c\x0f\xf1\xac\x7a\x79\x8b\x60\x1d\x62\xd9\x57\x67\x04\x79\x97\ -\xdb\x3d\x9a\xdb\x4c\xb7\x18\x85\x27\x6b\x5d\xdf\xbb\xa1\x00\xde\ -\x39\xae\x3e\xf0\xd5\xe4\xe5\x8b\x8d\x9a\xaf\xdd\x05\xa7\x6a\x85\ -\xea\x0e\x61\x5a\x90\xf2\xa4\x17\xc0\xde\xdd\xc1\x14\xe7\x15\xc2\ -\x7a\x4b\xe8\xad\x3d\x4f\x40\xe7\x05\x1c\xcd\x97\x69\x69\xaa\xc3\ -\xe1\x74\x9a\xda\x3d\x76\xaa\x98\xb5\x3f\x7d\x7d\x54\x62\x02\xc1\ -\xa2\xe4\xe3\xa2\x6a\xf7\xba\xd1\xf4\x74\x6f\x03\x7f\x3c\xa1\xb6\ -\x56\xb6\xb1\xa6\xa9\x47\x70\xb8\x21\x22\x00\x3a\xfa\x14\x15\x65\ -\x97\x39\x7a\xb5\x94\xaa\xab\xa5\x34\x35\xd4\xe0\xf4\x42\x12\xe0\ -\xd7\xcf\xaf\x66\xf9\xd2\x9c\xc1\xbf\x6b\x1b\x5a\x78\x73\xcb\xa7\ -\x7c\x71\xe2\x02\x7e\xbe\xde\xbf\xe8\x21\xb0\x20\xbc\x55\x54\xe3\ -\xb6\xde\x1e\x6b\xd9\x34\xee\x00\xde\x3f\xe1\x7e\xad\xc2\xc6\x93\ -\x0a\xcf\xfe\x75\x3a\xec\xfc\xe3\xf8\x71\x4e\x1e\x3b\x4c\x87\xad\ -\x75\x3c\x8e\x87\xc1\xee\x70\xb2\x6b\xdf\x51\xb6\xec\x3a\x48\x6f\ -\x9f\x63\xc2\xf3\x81\x37\x0e\x95\xba\xda\xf2\x53\xf4\xed\xd7\x0f\ -\x8c\x08\x60\xfb\xd7\xea\xae\xf2\x76\x7e\xac\xfa\xb7\xec\xb9\x93\ -\xc7\x38\x7c\xf0\x2f\xd8\xfb\x7a\x27\xe3\x98\x2b\xdf\xd4\xf2\x8b\ -\xd7\xde\xa3\xb6\x61\x52\x69\x7e\x00\x96\x0e\xa7\xf6\xee\xce\x0b\ -\xaa\xf6\xf1\x34\xf9\x6c\xe8\xc0\xb0\x6f\xe0\xaf\x85\x2a\xa0\xa1\ -\x4f\xed\x75\x19\x88\xdb\xed\xe2\xff\x3e\xd9\xc6\x81\xbd\x1f\x4d\ -\x9a\x3c\x40\x69\x45\xed\x8d\x92\x07\x20\x3e\x54\xf9\xa5\x84\x1a\ -\xbf\xff\xe0\x80\x0a\x1c\x2a\x1f\x16\x40\x9d\x9b\xdd\xed\x7d\x12\ -\xa0\x94\xc1\xbe\x4f\xb6\x51\x7c\xb6\xf0\x86\x1d\xdf\x2c\x28\x05\ -\x65\xcd\x92\xe9\x08\x57\x3b\x87\xca\x07\x03\xf8\xe8\x8c\x4a\x6f\ -\xec\xe2\x5e\x80\x63\x87\x0f\x72\xe9\xc2\xe9\x51\x0d\x66\xa5\x25\ -\xf3\x83\x55\xf7\xf3\x9b\x17\x9e\xc4\x47\xf7\x9a\x0b\x6e\x1a\x8e\ -\x55\x0a\xc5\x8d\x42\x7d\xa7\x3c\xb0\xfb\xa4\x9a\x3b\x20\x1f\xf4\ -\x6c\xeb\x51\x5b\x1d\x6e\x91\x8e\xd6\x06\x0a\x3e\x3f\xe8\xd5\xd0\ -\xec\xc4\x38\x7e\xbe\xee\x51\x66\x27\xc6\x0d\xca\x7e\xfb\x87\x8f\ -\x60\x8c\x03\xee\x46\xd0\xde\x07\x8d\x5d\x9e\xbc\xed\x70\x23\x0d\ -\x0e\xb5\x1d\xc8\x86\xfe\x5f\xa0\xa0\x5a\x45\xde\x12\xcd\xdc\xac\ -\x18\x45\x59\xe1\xdf\x71\x7b\x39\x64\xf2\x72\xd3\xd9\xfc\xf2\x86\ -\x61\xe4\xbf\x6d\x38\x0d\x28\x6e\x18\x7e\x61\x68\xee\x91\xac\x3f\ -\x5f\x50\x33\xa0\xff\x17\xb0\x6a\xc6\x6a\xab\x0f\xfe\xe2\xea\xe5\ -\xf3\x82\x33\xa6\x86\x66\x27\xc6\xf1\xd2\x8f\xfe\x15\xab\x6e\xb9\ -\x29\xc4\xe6\x26\xc7\x0f\x27\xd5\xd6\x4e\x6d\x43\x0b\xad\xb6\x2e\ -\x34\x11\x66\xc6\x46\x11\x18\x1e\xc5\xd7\x55\x16\x1c\xd7\xad\xa7\ -\xd3\x8d\x74\x76\x1b\x2f\x03\x4f\x78\xb6\x90\x52\x6b\x40\xf8\xaa\ -\xa8\x18\xa7\x6b\xe4\xea\x8b\x08\x2f\xac\x7d\xe8\xa6\xec\xf5\x88\ -\xf0\x10\xd6\x3f\xf1\x00\xcb\x96\x64\x53\xd7\xd8\xca\xa7\x07\xbe\ -\xe4\xf3\xaf\xcf\x51\x55\xd7\x34\x42\x37\x34\x2c\x82\xec\x3b\xbe\ -\x43\x66\x6e\x1e\x9a\x36\xfc\xd2\x60\xeb\x93\x07\x01\xf4\xc2\x0a\ -\x35\x1d\x8c\x6c\x80\xe2\xb2\x4a\x53\xa7\x73\x92\xe2\xc9\x48\x4d\ -\xb8\x21\xe2\x16\x8b\xc6\xc3\x2b\xf2\x58\xfb\xc8\x32\x00\x36\x6d\ -\xd9\xc3\xee\xbf\x7f\x39\xea\x9d\xa8\xdd\xd6\xca\x67\xfb\x76\x53\ -\x56\x72\x9e\x95\x8f\xad\xc5\xc7\xd7\x6f\x70\xac\xa3\x4f\x82\x76\ -\x9d\x55\x73\x74\x8b\x8f\x3b\x5f\x29\xcf\xcd\xa6\xb4\xa2\xd6\xd4\ -\x50\x5e\x6e\xc6\x0d\x91\xcf\x4a\x4b\xe2\x85\xb5\x0f\x93\x10\x1f\ -\xcd\xfe\xc3\x27\x78\x7b\xc7\xdf\x68\xb5\x75\x8e\x7b\x7e\x65\xf9\ -\x65\xf6\xef\xde\xce\x83\x8f\xad\x65\xe0\x12\xa6\x80\x1e\xa7\xf1\ -\x6f\xba\x52\x32\x1f\xa0\xca\x26\x54\x37\xf5\x98\x1a\x48\x4e\x88\ -\x99\x34\xf9\xbc\xdc\x34\x96\x2f\xcd\xe1\x72\x79\x35\xcf\xfe\xea\ -\x4d\xce\x5f\xae\x98\x94\x9d\xd2\x92\x73\x5c\x2a\x3e\x43\xea\x2d\ -\x99\x83\xb2\x5e\x97\xe4\xeb\x55\x36\xc9\xeb\xe8\x83\x8a\x36\xc1\ -\xe9\xe8\x33\x9d\x1c\x19\x1a\x3c\x61\x87\x33\x62\xa7\x91\x9a\x18\ -\x87\xdb\x50\x6c\x7c\xe7\x63\xf6\xfe\xf3\x38\x86\x31\x66\x41\x33\ -\x2a\x0a\x8e\xfe\x63\x58\x00\xdd\x0e\x49\xd0\x4b\x9a\x24\xbd\xbb\ -\xff\x7e\xe5\x6b\x35\xbf\x5d\xfb\xfb\xfb\x8e\xdb\x89\xbf\xbf\x2f\ -\x4f\xff\xcb\x32\x1e\x5e\xb1\x98\xfd\x87\x4e\xb0\xf9\xc3\x7d\x74\ -\x74\x76\x7b\xd5\xcf\xce\x48\x61\xd9\x92\x1c\x66\x27\xc6\x22\x08\ -\x27\xcf\x95\xf2\xfe\xae\x83\x74\x76\x8d\xdc\x0d\x8d\x75\xd5\xb4\ -\xb5\x36\x13\x1e\x31\x05\x00\xb7\x41\xb0\x9e\x13\xaf\x7a\x8e\x55\ -\x48\xa8\xc3\x0d\x4a\x33\xbf\xe2\xda\xda\xbb\x98\x11\x13\x35\x2a\ -\x71\x11\xe1\xde\x25\xd9\xac\x5f\xf3\x00\xf5\x4d\xad\x3c\xfb\xeb\ -\x4d\x5c\xba\x52\xe5\x55\x3f\x69\x66\x0c\x1b\x9e\xfe\x1e\x59\x69\ -\xc9\x23\xe4\xfe\x53\x66\xb1\xf1\x8d\x4d\xa6\xe7\xd1\x37\x57\x4a\ -\x08\x8f\x58\x0c\x80\xcb\xad\xac\x7a\xb8\x9f\xf2\xbf\x2b\x59\x51\ -\xdf\x25\x7c\x1a\x18\x40\xa3\x89\xb3\x86\x16\xdb\xa8\xe4\x67\x27\ -\xc4\xb1\xee\x89\xfb\x89\x9f\x3e\x95\xb7\xb6\xff\x8d\xfd\x87\x4f\ -\xa0\x94\x97\xca\x2d\xc0\x9f\x67\x1e\x5b\xc1\xca\x65\x77\xa0\x5b\ -\xcc\xcf\x94\x94\xc4\x59\x24\xa5\xa6\x73\xf9\xe2\xc8\x33\xa9\xb9\ -\xb1\x6e\xf0\xdf\x0e\x43\x3c\xc9\xd5\x57\x87\x99\x61\x8a\xf4\xc4\ -\xa9\xa6\x06\x8f\x1e\x3f\x8b\xdd\x05\x15\xad\xc2\x85\x7a\xe1\x62\ -\x83\xd0\x37\xe4\xea\xf0\xe6\x4b\xeb\xb9\x5c\x5e\xc3\xaa\xe7\x5f\ -\x61\xdf\xa1\xaf\x4d\xc9\x8b\x08\xf7\xe5\xcf\xe3\xc3\x4d\xbf\x60\ -\x76\x62\x2c\xff\xf1\x9f\x7f\xf0\xba\x20\x33\xc3\x15\xe9\xe9\xb7\ -\x9a\x8e\xb5\x36\x0f\x5f\x62\x1d\xe8\x02\x22\x00\x12\x67\x4c\x37\ -\x9d\xf4\x45\xe1\x79\x76\x1c\xa9\x24\x22\x7a\x16\xfe\x3a\xf4\xba\ -\xe0\x4a\x8b\xe0\x6f\x85\x8a\xab\x65\xec\xff\xeb\x27\x34\xd4\x99\ -\xa7\x60\x80\xd4\xa4\x78\x7e\xf8\xcc\x43\x44\x4f\x8d\xe0\x7f\x3e\ -\xd8\xcb\xc1\xa3\x27\xf1\xf5\xb1\x7a\xd5\xf7\xb7\xc2\xd2\xb4\x30\ -\x76\x9b\x8c\xd9\x7b\xaf\x7d\x1b\x3e\x9a\x32\x74\x84\x36\x94\x27\ -\x80\x79\x99\xa9\xa6\x06\x9d\x2e\x37\x3b\xb7\x6c\x66\xc1\x92\x7b\ -\x99\x91\x90\x82\x52\x8a\xc6\xba\x6a\x2e\x9e\x2d\xa4\xaa\xa2\xcc\ -\x2b\x91\x90\xe0\x40\xfe\x7d\xd5\x7d\xac\xc8\xcf\xe5\x93\xfd\x5f\ -\xb0\xe1\xbf\xde\xa6\xb7\x77\x5c\xdd\x12\xa6\x86\x07\x99\x73\x71\ -\x5e\xab\xe8\x74\x8b\x38\x75\xa5\xe4\x8a\xa0\x92\x00\xe2\xa7\x4f\ -\x25\x65\x56\x2c\xa5\x15\x23\x7b\x4b\x7d\xbd\x3d\x1c\x3e\xb0\x67\ -\x5c\xce\x35\x4d\xe3\xfe\xbb\x16\xf0\xec\xea\xfb\x69\x69\xeb\xe0\ -\xa9\x1f\xff\x9e\xca\x9a\x86\x71\xcd\x1d\x40\x4f\xaf\x79\x4a\x37\ -\xfa\x3f\x6c\x4d\x20\xc2\x9f\x1a\xcd\xd3\xab\xbc\x86\x35\x0f\xdf\ -\x33\x21\x47\xd7\x23\x26\x7e\x16\xab\x7f\xf0\x23\xd2\x96\x3c\x4a\ -\x7d\xa7\xc6\xa5\xf2\xea\x09\x93\x07\x68\xb5\x75\x99\xca\x23\x43\ -\xfc\xb9\x63\xa6\x62\x79\xaa\xc1\xed\x31\xc6\x5e\x0d\xd4\xf1\xa1\ -\x0a\xf9\x0b\x6e\x25\x2f\x37\x7d\xc2\x0e\x03\x82\x82\x59\xb1\xf2\ -\x71\x1e\x5f\xbb\x01\x8b\xc5\xc2\x8e\xf7\x36\xb1\xe7\xc8\x05\x26\ -\x7b\x76\x95\x56\x54\x9b\x07\x10\x16\x48\x54\x90\xc2\xc7\x02\x06\ -\xea\x98\x6e\xd5\x2d\x9f\x39\x5d\x86\xa2\xbf\x4b\x27\x22\xbc\xf8\ -\xfc\x6a\x7e\xfe\xda\x7b\x14\x9d\x2f\x1d\xd3\x91\x66\xb1\x90\x95\ -\xbb\x98\x45\xf9\x2b\x00\x38\x74\x60\x0f\xa7\x0a\x8e\x62\x18\x06\ -\x19\x99\x0b\x68\x99\x64\x39\x7d\xa4\xe0\x9c\xa9\x7c\x48\xa2\x51\ -\x3e\x56\xcb\x11\x3d\x63\x9a\x34\x14\x55\xbb\x0a\x11\xc9\x1d\x18\ -\x09\x08\xf0\xe3\x8d\x17\xd7\xf1\xda\xce\xaf\xf8\xe2\xe8\x61\xda\ -\xdb\x46\x16\xe5\x11\x53\xa6\x91\x3c\x27\x83\xcc\xdc\x45\x84\x84\ -\x86\x73\xf1\x4c\x21\x47\xfe\xb9\x97\xee\xce\x8e\x61\x7a\xd5\xed\ -\x13\x6f\x80\x9f\xbe\x78\x85\xd2\xab\xe6\x3d\xde\x5b\x52\x66\x0e\ -\xf0\x2f\xc8\x98\x26\x0d\x9e\x0b\xbe\xc8\x36\x20\x77\xa8\xa2\xc5\ -\xa2\xb1\x68\xf1\x12\xe6\x66\xe5\xd1\xd6\xda\x44\x6b\x4b\x13\x86\ -\xdb\x4d\x60\x70\x08\x61\x61\x91\x04\x04\x5d\xbb\x1f\xfd\x69\xcb\ -\x26\xaa\xae\x9a\x67\x23\x35\xc1\x2d\x64\x77\x38\x79\xfd\x7f\x77\ -\x9b\x9e\x25\x3e\x56\x2b\x0b\xb2\x3c\xe5\xb0\x20\xdb\xa0\xbf\x22\ -\x73\x2a\x6d\xa7\x55\x8c\x57\x80\xc1\x96\x85\xd3\x80\x2e\x3b\x20\ -\x42\x78\x64\x14\xe1\x91\xde\xaf\x12\xf5\xd5\xe6\x75\xc4\x68\x70\ -\x1b\xd0\xda\xe3\xe9\xf8\x0d\xc0\xe1\x72\xf1\xe2\xc6\x2d\x94\x57\ -\x9a\x9f\x29\x77\x2d\xca\x24\xc0\x73\x2f\xeb\x76\x28\xed\x4f\xd0\ -\x5f\x13\xcf\x8f\x93\x16\x51\xbc\x3b\x54\x59\x17\xcf\x09\xfd\x6d\ -\xe2\xe8\x55\x8d\x2f\x2b\x3c\x5b\xec\x62\xd9\x37\x7c\xff\x67\x6f\ -\xf0\x55\xd1\x45\x53\x5d\xab\x6e\x61\xcd\x43\x77\x7b\xfe\x10\xde\ -\x1e\x78\x10\x19\xa4\x28\x4a\xdb\xa8\xc4\xf8\x3e\xfd\xbf\x82\x08\ -\x2c\x9a\x65\xf0\x59\x99\x36\xa2\x26\xbd\x19\x30\x0c\x83\xe2\xb3\ -\x85\x14\xd8\x5a\xd8\x51\x53\xc2\xd9\x92\xf2\x51\xf5\x9f\x7e\x64\ -\x39\x33\x62\xa2\x50\xd0\xe9\xd6\xb5\x8d\x03\xf2\xc1\x00\x32\xe3\ -\xa5\xa6\xa8\xc6\xfd\xdf\xc0\x6f\x07\x64\x7e\x3a\xcc\x8d\x52\x9c\ -\xa9\x9b\xf4\x4b\x94\x57\x0c\x74\xfe\xc6\x83\xa5\x0b\x6e\x65\xf5\ -\xca\x3b\x01\x10\xc5\x4b\x43\xdf\xd6\x86\x15\x00\x46\x9d\xf6\xba\ -\x82\x53\x43\x65\xb3\xc2\x15\xc1\xe3\x2f\x07\x6e\x3a\xee\xcb\x9f\ -\xc7\x6f\x36\x3c\x39\x50\xd4\x17\x19\xf5\xda\x9b\x43\xc7\x87\x05\ -\x90\x93\x23\x4e\x0c\xed\x11\x60\x30\x17\x8a\x40\x56\xcc\xc4\x4f\ -\xa3\xd0\xf0\x48\xa6\x46\xc7\x4e\x8e\x35\x10\x12\x14\xc0\x2f\x9f\ -\x5b\xc5\x2f\x9f\x5b\x35\xd0\xca\xe9\xb2\xa0\x3d\x9e\x93\x23\xc3\ -\xfa\xf9\x23\x3e\xd3\xec\x78\x29\x3b\x59\xeb\x7a\x4a\x94\x7c\x0c\ -\x58\xc0\x93\x29\xe2\x42\xd5\xb8\x72\xba\x6e\xb5\x72\xfb\xfc\xa5\ -\x2c\x5c\xba\x0c\xab\x8f\x0f\x4d\xf5\x13\x7a\xb3\x23\x66\x5a\x24\ -\x0f\xde\xb3\x90\xef\xde\xb3\x90\xe0\xc0\xc1\x14\xe5\x06\xb5\xfa\ -\xb6\x58\xb9\x34\xc2\x9f\x99\x91\xec\x18\xfd\xd3\x93\xb5\xee\xf5\ -\xa2\xd8\x3c\x20\xcb\x89\x53\xc4\x86\xc0\xa5\x66\xc1\xe6\xe5\x74\ -\x4d\x9e\x93\x4e\xfe\xf2\x87\x08\x0d\x8f\x1c\x37\x61\x7f\x3f\x1f\ -\x92\x67\xc6\x92\x9d\x91\x42\x76\x46\x0a\xb7\xcd\x4d\xbc\xbe\x07\ -\xa4\x44\xd4\xba\xac\x18\x7d\xaf\xd9\xfc\x51\x97\xf4\x54\xb5\xeb\ -\x29\x25\xf2\x2e\x43\x02\x55\xca\xf3\xd4\x54\x69\xbb\x36\x75\xeb\ -\x5b\xaf\x92\x77\xf7\x03\x24\xa6\xdc\x32\xc2\x86\xcb\xe5\xc4\xd6\ -\xda\x4c\x6f\x4f\x37\x2e\xa7\x03\x43\x29\x74\x8b\x4e\x4c\xa4\x1f\ -\x77\xa7\x87\x11\x3e\x7a\xc3\xc0\xad\x14\xcf\x65\xc7\x59\x36\x7b\ -\x53\x18\x73\x4f\x9c\xaa\x75\xad\x34\x94\x7c\x20\x30\xe8\xa9\xbd\ -\x17\x0e\x95\x5f\x5b\x25\xc3\xed\x46\xf3\x52\x1e\x7a\x43\x5a\xb4\ -\x22\x25\x72\xd4\x57\xcb\x0e\x50\x6b\x6e\x8f\x35\x5f\xf9\x01\x8c\ -\xf9\x4a\x99\x15\xa3\xef\xd1\x0c\x2d\x7b\x68\x76\xf2\xbf\xae\xf6\ -\x9f\x28\x79\x1f\x0b\x24\x84\x8d\x4a\xbe\x48\x19\x5a\xf6\x58\xe4\ -\x61\x1c\x01\x00\x64\xc5\x4b\x69\x47\x8c\x36\x4f\x84\x0d\x0a\x3a\ -\x2d\xe3\x9a\x35\x12\x91\x81\x90\x1d\xa7\xf8\x4e\x92\x81\x97\x1e\ -\x71\x8f\x82\x97\x7c\x6d\xda\xc2\xec\x78\xf1\x5e\xea\x0d\xc1\x84\ -\x4f\xa8\xc2\x0a\x35\xdd\xe2\x63\xfc\x64\x5f\xb1\xb6\xc1\xee\x1e\ -\xff\xfc\xb4\x69\x8a\x94\x29\x5e\x57\xbd\x1b\x61\xb3\x4b\xd7\x7e\ -\x37\xd1\xff\x00\x32\xe9\x23\xf6\xc3\x53\x6a\x56\x97\xd3\x78\xb5\ -\xa3\x4f\xee\xeb\xb0\x4b\x90\x97\x2e\xca\x20\x16\x27\x18\x4c\x09\ -\x18\x26\x52\xa0\x0a\x14\xb2\xdd\xcf\xd0\x3e\x4c\x8b\x97\x89\x3f\ -\x7f\x72\x03\x01\x0c\xc5\xae\xb3\x6a\x4e\xbb\xdd\x58\xef\x72\x69\ -\x77\xf4\x38\xd5\x4c\xbb\x21\x21\x2e\xb7\xb2\xba\x0c\x11\x05\x58\ -\x35\x8c\xc5\x09\xc6\x37\x21\xbe\x72\x49\x94\x2a\x31\x44\x1d\xd7\ -\x2d\x96\x43\xb7\x45\x8b\x59\x1b\x6a\x42\xf8\x7f\xd2\xb5\x8d\x07\ -\x16\x9e\x74\x46\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x19\xd5\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x60\x00\x00\x00\x60\x08\x06\x00\x00\x00\xe2\x98\x77\x38\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x02\x0b\x00\x00\x02\x0b\ -\x01\xf7\x4d\xfa\xeb\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x19\x52\x49\x44\ -\x41\x54\x78\x9c\xe5\x9d\x79\x60\x95\xd5\x9d\xf7\x3f\xe7\x79\xee\ -\xbd\xd9\x57\x12\x02\xd9\x48\x08\x10\x20\xec\x84\x4d\x50\x36\x17\ -\x16\x99\x22\x52\xec\x54\x5b\x44\x5c\xea\x54\xa7\xd5\xa9\xd3\xce\ -\x74\x3a\xb5\x9d\xb7\x7d\x6b\x3b\x6f\xa5\x76\x46\xe5\xd5\xb8\x80\ -\x5a\x14\x45\x05\x51\x51\x16\x95\x45\xd6\x40\xd8\x24\x21\x10\x42\ -\xc8\x4a\xf6\xe5\xee\xf7\x39\xf3\xc7\x85\x90\xe5\xde\x9b\xfb\xe4\ -\xde\x04\x3a\xfd\xfe\x95\x9c\xe7\x9c\xdf\x39\xf7\xfc\xce\xf2\x3b\ -\xbf\xe5\x1c\xc1\x0d\x8a\x37\xf6\xcb\x54\x87\x91\x85\x4e\x8d\x5c\ -\x97\x53\xe6\x38\x34\x91\x6e\x77\xc9\x38\xa7\x26\x8c\x2e\x0d\x55\ -\x93\xa8\x0e\x0d\xa1\x00\xaa\x82\x34\x28\x38\x85\x82\x2b\x33\x4e\ -\x56\x0c\x4b\xe0\x88\x40\x16\x69\xc8\x33\x8a\x50\x0b\x5d\x66\x8a\ -\x72\xb3\x44\xd3\xf5\xfe\x4d\x9e\x20\xae\x77\x03\xae\xe2\x85\xaf\ -\x65\x86\x41\xe5\x01\xbb\x4b\x2e\x32\xdb\xc5\xa8\x56\x3b\xe1\x52\ -\xea\xa7\xa3\x0a\x98\x95\xa9\x11\x17\xd6\x39\x5d\x48\xce\x03\xdb\ -\x41\x6e\x57\x5d\xea\xf6\x71\x43\x44\x43\x50\x1a\x1e\x20\xae\x2b\ -\x03\x5e\xde\x27\x87\x49\x83\xf6\x74\xab\x4d\x2c\x6e\xb4\x8a\xd8\ -\xde\x74\xb8\x27\xcc\xca\x90\x24\x44\xf8\x24\xe6\x02\xb9\x57\x08\ -\xde\xc0\xa6\x6e\x9c\x98\x29\x1a\x83\x53\xb3\x7e\xf4\x3b\x03\x5e\ -\xdd\x25\x43\x0d\xd1\x3c\xd9\x68\x95\x8f\xd4\x9a\x45\x7a\xb0\x3a\ -\x3d\xcc\x00\x36\x27\x24\x44\x4a\x6e\x1a\xa2\x8b\xa8\x55\xc0\x66\ -\x34\xf9\xfa\x84\x54\xf5\x53\x21\x84\x16\x9c\x16\xf9\x87\x7e\x63\ -\xc0\x33\x7b\x64\x54\x84\xca\x1f\x9b\x2c\x72\xa5\xcd\x29\x8c\xc1\ -\xa2\x1b\x61\x82\x11\x89\x92\xc4\x70\xc9\xbe\x52\x85\xf1\xc9\x92\ -\x44\xdf\xa3\xdf\x17\x4e\x21\xe5\xef\x9b\x52\xd4\xb7\xe6\x0a\xe1\ -\x0c\x56\x1b\x7d\xa1\xcf\x19\xf0\xcc\x19\x19\x15\xd5\x28\xd7\xd5\ -\xb7\x89\x25\x76\x17\xaa\xde\xf2\x06\x15\x26\x25\x4b\xe2\xc3\x25\ -\x56\x27\x14\x54\x28\x4c\x4c\xd1\x08\x35\xb8\xbf\x1b\x55\x68\xb1\ -\xc2\xbe\x52\x05\x4d\xc2\x94\x34\x49\x5c\x98\xc4\xa0\x04\xd4\xec\ -\x62\x90\xbf\x6b\x4a\x56\x5f\xef\x6b\x46\xf4\x29\x03\x9e\x3f\x20\ -\x9f\x6a\x32\xf3\x1f\x16\x07\x21\xbd\xa5\x61\x50\x20\x3a\x14\x4c\ -\xaa\x64\x5a\x9a\xa4\xc5\x0e\x8a\x80\x48\x93\xfb\x7b\x8b\x0d\x76\ -\x97\x28\xd8\x5d\xd7\xca\xa4\x44\x4b\xa6\xa4\x05\x65\x6d\x2b\x10\ -\x28\x3f\x9c\x98\x22\xf6\x06\x83\x98\x27\xf4\x09\x03\xde\x29\x90\ -\x99\xa5\x8d\xf2\xcb\x16\x9b\x48\x0b\x16\xcd\x09\x83\x25\x19\xf1\ -\x9d\x3b\x55\x4a\xf8\xaa\x44\xa1\xc1\xd2\x39\x6f\x46\x9c\x64\x42\ -\x72\x90\x36\x17\x90\x48\xde\x50\x0d\xca\x4f\xc6\x0f\x12\x35\xc1\ -\x22\x7a\x15\x41\x67\xc0\xd1\x0a\xe7\xd2\x36\xbb\x78\xf5\xb3\x22\ -\x25\x36\x58\x34\x23\x4d\x70\xeb\x70\xdf\x7b\xa3\xc5\x09\x6d\x36\ -\x28\xa9\x77\xff\xa4\x20\xcd\x80\x8e\xa8\x13\xc8\xd5\x13\x53\x0c\ -\x1f\x06\x93\x68\xd0\x18\xf0\xcc\x19\x19\x15\xd9\x20\x77\x59\xec\ -\x4c\x6e\xb5\x0b\xa2\x42\x40\x93\xd0\x6a\x77\x8f\xd4\x40\xa0\x08\ -\x98\x31\x44\x62\x50\x24\x2e\x0d\x8e\x57\x2a\xcc\x1b\xd6\xaf\xc2\ -\xca\x55\x48\xe0\xcf\x21\x8d\xca\x53\x39\x39\xc2\x1e\x0c\x82\x41\ -\x61\xc0\xba\x7c\x39\x3b\xd2\x24\xf3\x92\x22\x65\xd6\xc9\x2a\x41\ -\x45\x8b\x60\xd1\x48\x0d\xa3\xe2\xee\xfc\x03\x17\x05\x55\xad\xfa\ -\xaa\x12\x40\xa8\x11\x2c\x8e\x6b\x69\x06\x15\x52\xa2\x24\x59\x03\ -\x24\xd1\xa1\xc1\x68\x79\x2f\x21\xe4\x41\x21\xd4\x7b\x26\x0e\x16\ -\x17\x02\x26\x15\x28\x81\xbc\x83\xf2\x21\x4d\xe3\xb9\x61\x89\x5a\ -\xe8\xbe\x52\x05\x9b\x13\x42\x0d\x70\xc7\x08\x8d\xea\x56\xc1\xe9\ -\x1a\x81\xd9\x0e\x4e\x3f\x07\x6c\x98\x11\x52\x63\x24\x99\x71\x92\ -\xb3\xb5\x82\xb2\x66\x41\x66\x9c\x24\x29\x4a\x12\x1f\x0e\x81\x09\ -\x37\x6e\x34\x34\xb5\x10\x17\x13\x15\x28\x99\x7a\xa4\x72\xe7\xa4\ -\x54\xf1\x75\x20\x44\x02\x62\xc0\x4b\x07\xe4\xd3\x8d\x56\xf9\x8b\ -\xb9\x59\x52\xd9\x7f\x51\x50\x73\x65\x94\x2b\x02\x26\xa7\x4a\x0a\ -\x2a\x04\x76\x97\x5b\x92\x19\x9d\x24\x31\xaa\x70\xb2\x4a\x60\xf3\ -\x20\xd8\x0d\x8c\x94\x64\x27\xba\x3b\xf9\x6a\xa3\x2e\xd4\x0b\x92\ -\xa2\x24\x61\x41\x3a\x35\x38\x5d\x2e\x36\x7d\xba\x97\x8d\x5b\xbf\ -\x64\xe3\xf3\xbf\x08\x06\xc9\x36\x29\xb4\xe5\x93\x93\x8d\x9f\xf6\ -\x96\x80\xa1\xb7\x05\x5f\x38\xe0\x7a\xfe\x52\x33\x8f\x4e\x4e\x81\ -\x26\x2b\x5c\x6e\xbb\xc6\x4b\x4d\x42\xe1\x65\xc1\xc0\x48\x49\x65\ -\xb3\xc0\xa9\xc1\xf1\xca\xee\xbc\x76\xd8\xed\x34\xd4\xd5\x80\xb5\ -\x11\x11\xd2\x44\xc9\x51\x33\xad\x66\x2b\x9a\x26\x69\x6d\xb3\x20\ -\x71\x6f\x1e\x61\x21\x26\x42\x42\x8c\x44\x84\x85\x12\x16\x16\xca\ -\xe0\x81\xf1\x0c\x49\x1e\x48\x72\xd2\x00\xbf\xdb\x7b\xf0\x58\x21\ -\x6b\x5e\xd9\xc4\xc5\x8a\x1a\xa2\xa3\x22\x7a\xfb\xb3\xbb\x22\x42\ -\x48\xe5\xc3\xfc\x72\xe7\xfd\x93\x52\x0c\x7f\xe9\x0d\x81\x5e\x31\ -\x60\xed\x7e\xd7\xda\x8a\x66\xe5\x61\x29\xa1\xbc\x19\xea\xda\x94\ -\xf6\x8d\x36\xdc\x04\xd3\xd2\x34\xea\xcc\x82\x0b\x0d\x02\xd7\x95\ -\x74\x87\xc3\x4e\x65\xd9\x05\x2a\x2e\x95\x50\x59\x56\xca\xe5\x9a\ -\x4a\x9a\x9b\x1a\x7a\xbd\x43\xe7\x8e\x1b\xc1\x9a\x7f\x7f\xb4\xc7\ -\x7c\x55\x97\xeb\x79\xee\xb5\x0f\xf8\xea\xc0\x89\x5e\xd5\xe3\x07\ -\x4c\x20\xde\xc8\x2f\x77\xca\x49\x29\x86\x0d\x7a\x0b\xeb\x66\xc0\ -\x0b\x07\xe4\x6f\x2a\x9a\x79\xf8\x6a\xbf\x55\x36\x77\x1e\xd9\x66\ -\x3b\xec\x3a\xe7\x5e\xa9\x2d\x6d\x6d\x14\x9e\x3a\xca\xf9\xa2\x53\ -\x5c\x2c\x39\x8b\xd3\xe9\xe8\x4a\xae\xcf\x60\x77\x38\xf8\xcb\xe6\ -\x5d\xac\xdf\xb4\x03\xab\x2d\x28\x02\x8b\x2f\x28\x08\xb1\xee\x48\ -\x85\xa3\x61\x72\xb2\x71\x9b\x9e\x82\xba\x18\xf0\xda\x21\xf9\xf0\ -\x85\x46\xfe\xb5\xa7\x41\x5b\x7a\xae\x90\xe3\x47\xf6\x51\x7c\xe6\ -\x04\x2e\x97\xcb\x77\xe6\x3e\xc0\xbe\xfc\xd3\xfc\x29\xef\x7d\xca\ -\xab\x6b\xfb\xaf\x52\x89\x51\xa0\x6c\xca\xbf\x24\x6f\xd5\xb3\x31\ -\xfb\xcd\x80\x75\x05\x72\x5e\x69\xad\x7c\x51\x93\xde\xf7\xed\x4b\ -\xa5\xe7\xd9\xbb\x73\x2b\x65\x17\x8a\xfd\x25\x1b\x54\x54\x54\xd7\ -\xf1\xe2\x1b\x5b\xd8\xf9\x75\xc1\x75\xa9\x1f\x08\x6f\x75\x68\x1f\ -\xad\x3f\x22\x97\x7e\x6f\xb2\xd8\xed\x4f\x01\xbf\x18\xb0\xf6\x73\ -\x19\x53\xd9\xc0\x16\x87\x4b\x78\xec\xfd\xc6\xfa\x5a\x3e\xdb\xf2\ -\x36\x17\xcf\x17\xe9\x69\x6c\xd0\x60\xb3\x3b\x78\xe3\xfd\x1d\xbc\ -\xf9\xc1\x4e\xec\x8e\xfe\x5b\xe6\x3c\x21\xdc\x48\x7c\xbd\x55\x6e\ -\xc9\xdb\x23\xd3\x56\xcf\x12\x2d\x3d\xe5\xf7\x8b\x01\xf6\x08\xf6\ -\xb5\xb5\x12\xde\xed\x83\x94\x1c\xda\xb7\x93\xbd\xbb\x3e\xc1\x79\ -\x1d\x7f\xf8\xd3\xcf\xae\x63\xf7\xa1\x93\xd7\xad\xfe\x8e\x50\x04\ -\x84\xa8\xc4\x28\x06\x36\x03\x73\x7b\xca\xdf\x23\x03\x5e\x3a\x20\ -\x7f\x5f\xd6\xc4\xe8\xae\xe9\x0e\xbb\x9d\x4f\xde\x7f\x83\xa2\xd3\ -\xd7\x6d\xba\xb7\xc3\xd2\xf7\x9b\xac\x2e\x4c\x4d\x93\x80\x9c\x7d\ -\xb4\xc2\xb9\x74\x62\xb2\xe1\x03\x5f\x79\x7d\x1e\x2c\x5f\xc9\x97\ -\xa3\xab\x5b\xf9\x49\xd7\xf4\xd6\x96\x26\xfe\x92\xb7\xe6\x86\xe8\ -\xfc\x1b\x18\xc2\xe1\x14\x79\xaf\xe4\xcb\x6e\x83\xb7\x23\x7c\x32\ -\xa0\xd9\xc2\x56\xbb\xab\xf3\x69\xd9\x6a\x6e\x63\xe3\xba\xe7\xa9\ -\xa9\x2a\x0f\x46\x23\xff\x57\xa3\xa4\x41\xc4\xb7\x9a\xe5\x66\x5f\ -\x79\xbc\x32\xe0\xd5\xc3\xf2\x47\xf5\x6d\x64\x74\x4c\xb3\xdb\x6d\ -\xbc\xf7\xe6\x5a\xea\x6a\xaa\x82\xd4\xc4\xff\x9d\x70\x6a\x50\x5c\ -\x27\x38\x53\x23\xa8\x35\x8b\xac\xbc\xc3\xf2\x71\x6f\x79\x3d\x32\ -\xe0\xd5\x12\x19\x5a\xd3\xca\xef\xba\xa6\x6f\xff\x68\x23\x95\x97\ -\x4a\x83\xd2\x48\x21\x04\x43\x52\x93\xc8\x1d\x37\x82\x5b\xa6\x8d\ -\xc5\x68\xd0\x6d\xad\xbc\x21\xd1\x62\x83\xed\x67\x15\x4e\x56\x5d\ -\xd3\x02\xd4\xb6\xc8\x3f\xbc\x5a\x22\x3d\xea\x6f\x3d\x6e\xc2\xce\ -\xcb\xda\x4b\x6d\x76\xa5\x53\x81\xa2\xd3\x05\x9c\x2e\x38\x14\x50\ -\xe3\x84\x10\x4c\x9f\x30\x92\x3b\xe7\x4f\x67\xfc\xe8\xa1\xc4\x46\ -\x47\xb6\x7f\x5b\xb4\xea\xdf\x70\xb4\xb4\x05\x44\xff\x7a\x43\x4a\ -\x38\x5c\xae\x60\xed\xa2\x6c\x6c\x73\x88\x10\xad\x56\x7b\x1e\x78\ -\xa0\x6b\x99\x6e\x0c\x78\xba\x44\x86\xd6\x96\x72\x4f\xc7\x34\x8b\ -\xb9\x95\xcf\x3f\x7a\xbb\xd7\x0d\x13\x42\xb0\x70\xce\x14\xee\xbb\ -\x6b\x3e\xe9\xc9\x03\x7b\x4d\xe7\x46\x47\x75\xab\xa0\xc9\xe2\xf9\ -\x5b\x4d\x8b\xf8\xde\xda\xc3\xf2\xb1\x47\x72\x85\xb9\x63\x7a\x37\ -\x06\xa4\xd5\xf2\xa7\x52\x3b\x9d\x14\xc0\x27\x0e\xec\xc4\xd2\xd6\ -\xbb\xd1\x39\x70\x40\x2c\xff\xfe\xa3\xfb\x98\x30\x3a\xab\x57\xe5\ -\xff\x9a\xd0\xe6\x43\x1a\xb6\x38\x85\x01\xa7\xf6\x67\x60\x75\xc7\ -\xf4\x4e\x7b\x80\x94\x52\x64\xc4\x69\xf3\x26\xa5\x4a\x92\xa3\x25\ -\xe1\x46\x48\x0b\x6f\xe1\xd0\x7e\xbf\x4e\xd5\xdd\x30\x32\x2b\x9d\ -\xbc\xdf\x3f\xf9\x37\xd1\xf9\x0e\xed\x9a\x3d\xda\x1b\x9a\xac\xca\ -\xbd\xef\x48\xd9\x69\xb3\xeb\xc4\x80\x63\xe5\xae\x45\x71\x61\x0c\ -\x4b\x8f\x91\x4c\x4d\x93\xdc\x3e\x42\xe3\xd0\xbe\x5d\x58\xac\xfa\ -\x0f\x3a\xa3\x86\xa5\xf3\xdc\x2f\x1f\x0d\x86\xe5\xe9\x86\x47\x9d\ -\x19\xbe\x3a\xaf\xd0\xda\x43\x37\xb5\xda\x09\xb1\xe6\x6b\x3f\xeb\ -\x98\xd6\x79\x06\x28\xca\xca\x8e\xff\x6b\x9a\x64\xdb\x97\x87\x75\ -\x37\x68\xe0\x80\x58\x7e\xf7\xb3\xd5\x84\x87\x5f\x4f\xc3\x6d\xdf\ -\xa3\xcd\x0e\xfb\x4a\x05\x7b\x4a\x14\x5a\x6c\xfe\x95\xa9\xb7\x28\ -\x9d\x8c\x18\xed\x7b\xc0\xe1\x73\x32\x06\xa9\xdd\xd9\xf1\xe3\xa9\ -\xb3\x17\xb8\x5c\xaf\xcf\xab\x5b\x08\xc1\xcf\xfe\xe1\x3b\x0c\x88\ -\x8d\xd6\x55\xae\xbf\x30\x34\x3d\x99\x27\x1f\x5c\xe6\xf5\x7b\x45\ -\x75\x1d\x45\x25\xe5\x94\x96\x57\x51\x53\xdb\x44\x4b\x9b\x19\xa7\ -\xcb\x45\x88\xc9\x48\x54\x44\x38\x99\x69\x83\x18\x96\x91\xcc\xf0\ -\xa1\xe9\x1c\xb9\x64\xa4\xde\xcb\xa6\xeb\x0d\x8d\x66\x52\x5e\x3b\ -\x2c\xb3\xee\xcf\x15\xe7\xa0\x03\x03\x44\x88\xf6\x1d\xa0\x93\x53\ -\xf7\x17\xbd\x50\xeb\xde\x3a\x73\x22\x53\xc7\x67\xeb\x2e\xd7\xd7\ -\x88\x08\x0f\x63\xf5\x3d\x0b\xb8\x7b\xc1\x2c\x54\xf5\xda\xc4\x77\ -\xba\x5c\x1c\x3a\x5e\xc4\xae\x7d\xc7\x38\x7c\xbc\x88\x9a\x3a\xff\ -\x1c\xa5\xc3\x23\x22\xc8\xce\x99\xc4\xf8\x29\x33\x49\x18\x38\xd8\ -\xef\x76\x68\x80\x4d\xd3\x9e\x06\xbe\x07\x1d\x19\x20\xe4\xbd\x5d\ -\x6d\xf4\xfb\x8f\x9d\xf1\x9b\x30\x80\xa2\x28\xac\x5a\x71\x87\xae\ -\x32\x7d\x0d\x21\x04\x0b\x66\xe7\xf2\xe8\x7d\x4b\x88\x8f\xbd\xb6\ -\x1f\x99\xad\x36\xb6\x6c\xdf\xcf\xdb\x5b\xbe\xf0\xbb\xd3\x3b\xc2\ -\xdc\xd6\xc6\xd1\x83\xbb\x29\x38\xbc\x97\xf1\xb9\x33\x99\x39\x6f\ -\x11\xa1\x61\xdd\x15\xc6\x9e\xd0\x6a\x53\xda\x57\x1a\x03\x5c\x59\ -\x7e\xd0\x6e\xea\x98\xc9\x6a\xb3\x53\x56\x71\x59\x57\xa3\xa6\x4f\ -\x18\x79\x43\xc9\xf9\x23\x32\x53\x79\xe2\xc1\x65\x8c\xcd\xce\x6c\ -\x4f\x73\xb9\x34\xde\xdf\xb6\x97\x57\x36\x6e\xa3\x39\x08\x07\x3f\ -\x4d\xd3\x38\x7a\x70\x37\xe7\x8a\x4e\xb1\xfc\xbe\x1f\x10\x9f\x98\ -\xd4\x63\x99\x66\x2b\xb1\x6b\x0f\xcb\xf4\x47\x72\xc5\x45\x03\x80\ -\x12\xea\x9a\x0d\xa2\x93\x78\x74\xbe\xac\x12\x4d\xd3\xe7\x7d\xb6\ -\x68\xde\x34\x5d\xf9\xfb\x0a\x51\x11\xe1\x3c\xf4\xdd\x45\x2c\xbd\ -\x6d\x06\x8a\x72\x6d\xb9\x29\xf8\xe6\x1c\x7f\x7c\x79\x13\xe7\x4a\ -\x2b\x82\x5e\x67\x73\x63\x3d\x6f\xe5\xad\x61\xd9\x7d\x8f\x90\x9c\ -\x9a\xe1\x33\xaf\x94\x60\x70\x69\x8f\x00\x3f\x37\x00\x08\x29\xe6\ -\x76\xb5\x34\x9e\x3d\xaf\x4f\xdb\xa9\xaa\x0a\x53\xc7\x8d\xd0\x55\ -\x26\xd8\x10\x42\x70\xfb\x2d\x93\x79\xec\xfb\x7f\xd7\x49\xfc\xad\ -\x6b\x6c\xe6\xf9\xf5\x5b\xf8\xec\xab\x23\xc8\x60\x45\x84\x78\x80\ -\xd5\x62\xe6\x83\xb7\x5e\xe6\xfb\x8f\x3e\x45\x64\x54\x8c\xcf\xbc\ -\x16\xa7\x58\xcc\x55\x06\x48\xc1\xbc\xae\x19\x4a\xab\xea\x74\x55\ -\x3e\x72\x68\xfa\x75\x15\x3b\x47\x66\xa5\xf3\xe4\x43\xcb\x18\x3d\ -\x6c\x48\x7b\x9a\xd3\xe5\x62\xe3\xd6\xaf\x78\x65\xe3\x36\x2c\x16\ -\x3f\xe5\xc4\x00\x61\x6e\x6b\x61\xeb\x7b\xeb\x58\xb1\xf2\x87\x08\ -\xe1\x5d\xdb\xdf\x6a\x17\xd9\x00\x86\x2b\xeb\xff\xd8\x8e\x1f\xcf\ -\xd7\x0b\x0a\x2b\xf5\x35\x38\x2b\xc3\x7f\x49\x20\xd8\x78\xe0\xdb\ -\x77\x30\x26\x3b\x13\x45\xb9\x36\x8d\x8f\x9c\x38\xcb\x1f\xf3\xde\ -\xa3\xf4\x52\x75\xbf\xb7\xa7\xac\xa4\x98\xfc\x03\xbb\x99\x3c\x7d\ -\xb6\xd7\x3c\x16\x27\xa1\x9b\x8a\xe5\x40\x83\xc1\x44\xb6\xd6\x41\ -\xfc\xb1\x3a\xe1\x74\x8d\xc0\x66\xd1\x27\xe0\x5e\xcf\xcd\x77\xdc\ -\xa8\xa1\xed\x7f\x57\xd7\x36\xf0\x5f\xaf\x7d\xc8\xae\xfd\xd7\xd7\ -\x5a\xf7\xf5\x97\xdb\x18\x37\x69\x3a\x46\x93\xe7\xd8\x14\x29\xa1\ -\xb1\x91\x85\x06\x4d\x75\x65\x23\x05\x1a\xd0\x6a\x85\x53\x35\x02\ -\xa7\x0b\x6c\x36\xab\xae\x0a\x13\xe2\x7d\xaf\x79\xc1\x46\x4c\x54\ -\x67\x91\xcf\xee\x74\xb2\xe1\xc3\x2f\x58\xb7\xe9\xf3\xfe\x70\xc4\ -\xea\x11\x56\x73\x1b\xc7\xf3\xf7\xfb\x9c\x05\x4e\xa7\x76\x8b\xe1\ -\x64\xa5\x72\x4f\x9d\x59\xd0\x68\xed\xec\x25\xe8\xb0\xeb\x63\x40\ -\x78\x68\xaf\xa3\x90\x74\x21\x32\x3c\x8c\xd5\xdf\x59\xc0\xb2\x3b\ -\x66\xb5\xa7\x7d\x7d\xf4\x1b\xfe\x94\xb7\x89\x4b\x55\xfd\xe8\x88\ -\xe5\x07\x4e\xf4\xc4\x00\x29\x46\x1a\x1a\xad\x62\x7c\xd7\x10\x1f\ -\x00\x45\xd5\xe7\xb5\x18\x62\x0a\x5a\xe0\xa3\x47\x28\x8a\x60\xd1\ -\xdc\xa9\x3c\xf2\xdd\xc5\xed\x12\x4e\x45\x75\x1d\x7f\x7a\xf5\x7d\ -\xf6\x1e\x3e\x15\x30\xfd\xb8\x98\x28\x66\xe6\x8e\x26\x33\x6d\x30\ -\xc9\x49\x03\x08\x0b\x31\x51\xdb\xd0\xcc\xb1\xd3\xc5\xec\xd8\x77\ -\xac\x57\x9b\x78\x6d\x75\x05\x8d\x0d\x75\xc4\xc6\x79\x76\x22\x76\ -\x6a\xa4\x1a\x9c\x12\x8f\xea\xca\xb0\x10\x7d\x23\xda\xe1\xe8\xbb\ -\x60\xc2\xd1\xc3\x86\xf0\xc4\x83\xcb\x18\x35\x2c\x1d\x08\xae\x23\ -\xd6\x8c\x89\xa3\xf8\xfb\x6f\xcd\x65\xc2\xe8\xac\x4e\x67\x86\xab\ -\x58\x30\x3b\x97\x1f\xdc\x7b\x27\xbf\x7f\xf1\x6d\xbe\x3a\xa8\xdf\ -\xf7\xe8\x5c\xe1\x49\xaf\xb3\xc0\xe1\x12\xb1\x86\x99\xe9\x5a\xc5\ -\x91\x72\x11\x53\xd5\xd2\xf9\x20\x20\x8c\x26\x5d\x15\x59\xec\xc1\ -\x5f\x77\xe3\x62\xa2\xf8\xc1\xbd\x8b\x59\x38\x67\x6a\xbb\x84\xf3\ -\xd5\x81\x13\xfc\xf9\xf5\x0f\xa8\xac\xa9\x0f\x88\xf6\xd8\xec\x4c\ -\x1e\xbf\xff\x5b\x8c\x1e\x3e\xa4\xc7\xbc\xb1\xd1\x91\x3c\xfd\xe4\ -\x2a\x1e\xff\xcd\x7a\x4e\x9d\x38\xa6\xab\x1e\x5f\x0c\xb0\x3b\x65\ -\xa8\xc1\xa8\x12\x3e\x25\x4d\xb2\xaf\x54\x50\xd7\xe1\x64\x6e\xd2\ -\xc9\x80\xfa\x86\x1e\xbd\xf0\xfc\x86\xaa\x2a\xdc\xbd\x60\x16\x0f\ -\xdc\xb3\x80\xc8\x70\xb7\x7e\xf0\x62\x45\x0d\x6b\xf2\x36\x71\xb0\ -\xa0\x30\x20\xda\xf1\xb1\x51\x3c\x7a\xdf\x12\x16\xcc\xce\x45\x78\ -\xf6\xb4\xf4\x88\x46\xab\xca\xbc\x3b\xbf\x43\xe9\x85\x12\x5a\x5b\ -\xfc\xd7\x10\x57\x55\x94\x79\xfd\x26\x11\xaa\x01\x88\x52\x05\xcc\ -\x1c\xa2\x51\xd9\x22\xb8\xdc\xea\xb6\x6d\x9a\xbc\x88\x4f\xde\x50\ -\x56\x19\x9c\x08\xce\x49\x63\x86\xf3\xc4\xea\x65\x64\xa6\x0d\x02\ -\xdc\x4a\xb3\xd7\xdf\xfd\x9c\x77\x3e\xfa\x02\x87\xb3\xf7\x9e\xd6\ -\xaa\xaa\x70\xf7\xc2\x9b\x59\xbd\xe2\x0e\x22\xc2\xc3\x7a\x2e\xd0\ -\x05\x03\x22\x24\x61\xa1\x61\xe4\xde\x34\x97\x2f\xb6\xf9\x74\x76\ -\xeb\x04\xbb\xd5\x42\x6b\x4b\x93\xc7\x93\xb1\x4b\x43\x31\x00\x91\ -\xe0\xf6\x69\x4c\x89\x96\xa4\x44\x03\x48\xea\xb3\xe3\x38\xac\x23\ -\xfa\xe9\x4c\xb1\x77\x4e\xfb\x83\xa4\x01\xb1\xfc\xe4\xe1\x6f\x33\ -\x6f\xc6\xf8\xf6\xb4\xed\x7b\x8f\xf2\xdf\xaf\x7f\xa8\xdb\x26\xd1\ -\x15\x13\x73\xb2\x78\x62\xf5\xdd\x0c\x4d\x77\x1f\x16\xed\x4e\x27\ -\x26\x83\x3e\x21\x43\x00\x52\xc0\x98\x09\x53\xf9\xf2\xb3\xcd\x48\ -\xe9\xbf\x9e\xac\xfe\x72\xb5\x37\x06\x08\xaf\xad\x18\x76\x65\x04\ -\xfa\x8b\xd3\xc5\x17\x69\x69\x33\x13\x15\x71\x4d\x3e\x77\x68\x70\ -\xa2\x52\x30\x3c\x41\xa2\x0a\x77\x94\xa3\xc9\x8b\xfb\xcf\xda\xdf\ -\xfd\xb8\xbd\x53\xce\x5f\xac\xe4\xd9\xbc\xf7\x38\x7a\xea\x9c\xae\ -\x36\x74\x45\x62\x7c\x0c\x3f\x5c\xf9\x2d\x6e\x9d\x39\xb1\x3d\x6d\ -\xe7\xd7\x05\xac\x7f\xef\x73\x5e\xfd\xcf\x6e\x1e\x97\x3e\xa1\x08\ -\x18\x91\x20\x29\x94\x11\x0c\x4e\x49\xa7\xe2\xd2\x05\xbf\xcb\xd6\ -\x5d\xae\x26\x7d\xa8\x67\x3d\x99\x01\x68\x05\xe2\xbb\x7e\x18\x92\ -\xa2\xef\x64\xab\x69\x1a\x1f\xef\x3a\xc4\xf2\xc5\xb3\xb9\xd4\x28\ -\xa8\x35\x43\x4d\xab\xc0\xe1\x82\xc8\x10\x28\xaa\x15\x20\x61\x4c\ -\x52\xf7\x88\x77\x4d\x82\xaa\x1a\x68\x33\x5b\xc8\x7b\x67\x1b\xef\ -\x7d\xb2\x1b\x97\xab\xf7\x71\xc0\x46\x83\xca\x8a\x3b\xe7\xb0\x72\ -\xf9\x6d\xed\xe7\x93\x92\xb2\x2a\x9e\xcd\xdb\x44\xfe\xc9\xb3\xbd\ -\x8e\x11\x1b\x35\x50\x62\x52\x21\x29\x39\x4d\x17\x03\x9a\x9a\x3c\ -\x0b\x0c\xaa\x82\x34\x00\x2d\x78\x60\x40\xe2\x80\x18\xc2\xc2\x42\ -\x74\xc9\xbf\x1b\x36\xef\x62\x68\xce\x34\xca\xcd\xd7\x66\x41\xb8\ -\x09\x8a\x6b\xdd\xa7\x6b\x80\x63\x95\x82\xc2\x5a\x81\xc0\x1d\x8e\ -\x6a\x34\x40\x61\x35\x9c\x3c\x76\x88\xdd\xdb\x37\xd3\xd4\x1c\xd8\ -\x66\x3e\x75\x7c\x36\x3f\x5e\xbd\xac\x5d\x35\xd2\x6a\xb6\xf0\xca\ -\xdb\x9f\xf2\xde\xa7\x7b\x02\x62\xea\x55\x64\x0d\x90\x4c\x1c\x96\ -\xc8\xd1\x83\xfe\x97\x71\xd8\x3c\xf7\xa1\xaa\xa0\x5d\x9d\x01\xdd\ -\x20\x84\x60\xe4\xd0\x54\x5d\xcb\xc0\xe5\xfa\x26\xd6\xbe\xf6\x36\ -\x8b\xbf\xbd\xb2\x5d\x13\x18\x6a\x00\x2b\x74\xba\x4c\xe3\x6a\xf0\ -\x75\x51\xad\xa0\xe2\xd2\x05\xbe\xfc\xf4\x03\xca\xcb\x4a\xfc\xae\ -\xc7\x13\x06\x0f\x8c\xe7\xf1\x95\x4b\xb9\x65\x9a\x5b\xaf\xa8\x69\ -\x92\x4f\xbe\x38\xc8\x8b\x6f\x6e\xa5\xa1\x29\x78\x12\x1a\x40\xc6\ -\x60\x7d\xf6\x6e\xbb\xdd\x33\x03\x84\x22\x5d\x06\x90\xcd\xde\xc2\ -\x85\x67\xe6\x8e\xd1\xbd\x0e\x9f\x39\x75\x0c\x97\xcb\xc5\x6d\x7f\ -\x77\x0f\xe1\x11\x51\xd4\x9b\xbb\xe7\xd1\x34\x8d\x8b\x25\x45\x1c\ -\xde\xb7\x8b\x0b\xc5\xfa\xcc\x9e\x5d\x61\x32\x1a\xb9\x77\xe9\x3c\ -\xee\xbb\x6b\x7e\xfb\x69\xfc\x9b\xe2\x8b\x3c\xfb\xf2\x26\x4e\x17\ -\x07\xc7\x8f\xb5\x2b\xc2\x42\xf5\x89\xe8\x4e\x2f\x0c\x08\x51\xb1\ -\x1a\x10\x4a\xb9\xb7\x50\xd1\x59\x53\xc6\xf0\x5f\xaf\xeb\xbf\x9b\ -\xe2\xec\x99\x13\x94\x9e\x2f\x62\x44\xce\x44\x52\x87\x64\x12\x19\ -\x15\x8b\xdd\x6e\xa3\xa9\xa1\x96\xda\xcb\xd5\x9c\x2f\x3a\xd9\x6b\ -\x4f\xbb\x8e\x98\x99\x9b\xc3\x8f\x56\xdd\xd5\x1e\x2f\xdc\xd0\xd4\ -\xc2\xda\xb7\xb6\xf2\xf1\xae\x83\x68\x5a\xdf\x19\x5e\xf4\x4a\x50\ -\x76\x87\xe7\x43\xaa\x41\xa1\xd1\x20\x90\x85\xde\x9a\x9a\x3a\x28\ -\x81\x8c\xb4\x41\x5c\x28\xd3\xef\x8e\x6e\xb7\xdb\x38\x79\x74\x3f\ -\x27\x8f\xee\xd7\x5d\xb6\x27\xa4\x0e\x4a\xe0\x47\xab\x97\x31\x63\ -\xe2\x28\xc0\x6d\xe7\xdd\xb4\x6d\x0f\x79\x1b\x3e\xa5\xd5\xac\xd3\ -\x4f\xa4\x17\xd0\xab\x6d\x75\x79\x09\xcf\x35\x28\xe2\x92\x41\x22\ -\x0b\x7d\xdd\x58\xb0\x64\xfe\x74\xfe\xfc\x9a\xff\x07\x8f\xbe\x84\ -\xc9\x64\xe2\xae\xc5\xb7\xf1\xe0\x8a\x39\x84\x19\xaf\x8d\xc2\x5f\ -\x3f\xb7\x9e\x1d\x7b\xf5\xa9\x08\x02\x81\x59\xa7\x62\xce\x68\xea\ -\x6c\x29\x0c\x37\x42\x5a\xac\x64\x40\x98\x7c\xc7\xa0\xb8\xd4\x42\ -\x4d\xf1\x2e\x1d\x2c\x99\x37\x8d\xd7\xdf\xfd\x8c\xe6\x56\x0f\x8b\ -\x79\x3f\x22\x7b\xf4\x04\x66\x2f\x58\x4a\x74\x4c\x1c\x5f\x97\x42\ -\x6e\x9a\x46\xf4\x95\xc3\x7a\x53\x4b\xff\xb6\xed\x72\xbd\x3e\x37\ -\x96\xe4\xf8\x30\x46\x24\xb8\xaf\x5c\x4b\x8a\x82\xe4\x28\x89\x10\ -\x20\x85\xb2\x47\xb1\x86\x73\x06\xf0\xba\x60\x86\x87\x87\x72\xff\ -\xf2\xdb\x03\x6d\x73\xaf\x11\x9f\x98\xc4\x8a\x95\x3f\x64\xc9\x3d\ -\xab\x88\x8e\x89\x03\xa0\xc9\x2a\x79\x73\xe7\x85\xf6\xcb\x41\xfa\ -\x1b\x65\x95\xfa\xdc\x75\x06\xc6\x86\x32\x3a\x49\x32\x29\x45\x92\ -\x12\xed\xee\x7c\x40\xb3\x18\x29\x32\x4c\x1f\x20\x9a\xf3\xcb\x5d\ -\x27\x80\x71\xde\x08\x2c\x5f\x74\x33\xbb\x0f\x9d\x08\xf8\x64\xaa\ -\x07\x46\x53\x08\x33\xe7\x2c\x60\xd2\xf4\xd9\x28\xea\xb5\xe3\x73\ -\x65\x79\x29\x3b\x3e\x7a\x97\x90\xb0\x30\x26\xe4\x3c\xca\xc0\x48\ -\x1f\x44\xfa\x08\xc5\x17\xf4\xb9\xb5\x44\x46\x74\xd7\x3d\x49\x28\ -\x98\x95\x28\x5a\x14\x00\x21\xd9\xe9\x8b\x80\xa2\x28\xfc\xea\x89\ -\x95\xfd\xe6\xef\x99\x35\x22\x87\x07\x1e\xfb\x57\x72\x67\xce\x6b\ -\xef\x7c\xab\xc5\xcc\xce\x8f\x37\xf1\xd6\x4b\x6b\xa8\xaa\xb8\x08\ -\x40\x5d\x9b\xa0\xad\x9f\xc3\x93\xdb\xcc\x16\x0a\xcf\x5f\xd2\x55\ -\x26\x75\x50\x82\xa7\xe4\x1d\x70\xc5\x33\x4e\x0a\xb9\x0b\xc4\x8f\ -\x7d\x11\x89\x8f\x8d\xe2\xb7\x3f\x5d\xc5\x53\xbf\x7d\x39\x28\x1e\ -\x65\x9e\x90\x38\x28\x99\xf9\x8b\x97\x93\x9a\x7e\x2d\x9e\x40\x4a\ -\x8d\x82\xc3\xfb\xd8\xbd\x7d\x2b\x36\x6b\xe7\xb5\xde\xa9\x41\x41\ -\x45\xff\x2e\x43\xfb\x8f\x16\xea\x76\x58\x1b\x36\x24\xb9\x5b\x9a\ -\xa2\xc9\x5d\x70\x85\x01\x9a\x55\xfd\x52\x09\xd5\x5c\xe0\xfb\x5e\ -\xcf\x9c\xe1\x19\xbc\xf8\x7f\x1e\xe7\x9f\x7e\xf3\xff\x03\x36\x88\ -\x74\x44\x68\x78\x04\x37\xcd\x5e\xc0\x84\xa9\xb3\x3a\x59\xa5\xca\ -\xcb\x4a\xd8\xb1\xf5\x5d\x6a\x2a\xbd\x8f\xb8\x56\x9b\xf0\xbe\x81\ -\xf5\x01\x3e\xfe\xe2\x80\xae\xfc\xaa\xaa\x90\x99\xde\x4d\xb1\xe9\ -\xb4\x85\xab\x7b\xe0\x0a\x03\x72\xb3\x44\x53\x7e\xb9\x73\x2f\x88\ -\x5b\x7a\x22\x98\x9e\x92\xc4\x2b\x7f\xf8\x27\x9e\x5d\xb7\x8d\xed\ -\xbb\xf6\xe8\x1e\x0d\x1d\x11\x1d\x13\xc7\xe4\x9b\xe6\x32\x76\xd2\ -\xf4\x4e\xf6\x07\x73\x6b\x0b\x5f\x7e\xbe\x99\x53\x05\x87\x7a\xbc\ -\x4f\xc8\xec\x80\x06\x73\xff\xcc\x82\xd2\x4b\xd5\x1c\x2a\xd0\x77\ -\x1f\xc6\x90\x94\x41\x98\x8c\x9d\xed\xe5\x52\x88\xdd\xd3\x07\x88\ -\x66\xe8\xe0\x1d\x2d\x85\x78\x53\x48\x7a\x64\x00\xb8\x7d\x2f\x67\ -\xdd\x7e\x37\x29\xa3\x66\x52\x70\x78\x2f\xdf\x1c\xcf\xc7\x62\xf6\ -\xa8\x52\xea\x86\x98\xd8\x78\x86\x64\x65\x33\x22\x67\x02\xe9\x99\ -\x23\x3a\x8d\xf8\xab\x8e\xae\x7b\x77\x7d\x82\xdd\xea\xff\x81\xca\ -\xd5\x4f\x53\xe0\xa5\x0d\x1f\xeb\x1e\x70\xd3\x27\x8e\xec\x96\x26\ -\xa4\xf6\xc6\xd5\xbf\xaf\x31\xc0\xa2\xbc\x2d\x42\xb5\x35\x74\x89\ -\x11\xf0\x06\x45\xc0\x80\xc4\x41\xcc\x5b\x78\x37\x73\x6e\x5f\x4a\ -\x55\x65\x19\x97\xab\xca\xa9\xbb\x5c\x85\xcd\x6a\xc5\xe9\xb0\xa3\ -\xa8\x06\x42\xc3\xc2\x88\x8a\x8a\x25\x3e\x31\x89\xc4\x41\x29\xc4\ -\xc4\x76\x53\xbc\x02\x50\x56\x7a\x8e\x1d\x5b\xdf\xa5\xb6\x3a\xf8\ -\x8e\xb3\xc1\xc0\xbe\x23\xa7\xf9\x62\xff\x71\xdd\xe5\xe6\xcc\xe8\ -\x26\x5c\x5a\x85\x5d\xdd\x74\xf5\x9f\x76\x06\xe4\x66\x89\xa6\xa3\ -\xe5\xae\x2d\x12\x56\xf8\x43\x78\x6c\x92\xc6\xee\x0b\xee\xd1\xab\ -\xa8\x2a\xc9\xa9\x19\x3d\x7a\x05\xfb\xc2\x87\x1b\xf2\xb0\x9a\x6f\ -\xcc\x38\xe1\xcb\xf5\x4d\x3c\xf3\x82\xfe\x30\xdd\x41\x89\xf1\x8c\ -\xca\x4a\xef\x9a\xfc\x41\xc7\xeb\xf2\x3b\xfb\x61\x68\xf2\x75\x7f\ -\x08\xbb\x24\x9c\xad\xbd\x61\xde\x7e\xd0\x0d\x97\xe6\xff\x55\x75\ -\xad\x66\x0b\x3f\xfd\xbf\x2f\x53\xd7\xd8\xac\xbb\x1e\x8f\x86\x7f\ -\x45\xae\xeb\xf4\x6f\xc7\x7f\x26\xa4\xaa\x9f\x48\x49\x8f\xce\x2f\ -\xf5\x66\xfd\x17\xb1\xde\x48\x70\x49\x77\x70\x5d\xb3\xcd\x37\x23\ -\xea\x1b\x5b\xf8\xc7\xa7\x9f\xa7\xa8\x44\x9f\xdc\x0f\x10\x16\x16\ -\xc2\x8a\xc5\xdd\xb6\xd4\xd3\x13\x07\xa9\x9d\xee\x94\xeb\xc4\x00\ -\x21\x84\x54\x14\xd9\xed\x8e\x88\xae\x88\x09\x95\x28\x7f\xbd\xfd\ -\x0f\xb8\xaf\xd9\xdc\x59\xac\xb0\xf9\x1b\x85\xae\x3e\x51\xe0\xf6\ -\xae\x5e\xf5\x93\xff\xa4\x48\xe7\xa1\xeb\x2a\x96\x2f\xbc\xd9\x83\ -\xe9\x53\xfe\x47\xd7\x07\x22\xba\x29\xb6\xcf\x0e\x56\x37\x0c\xab\ -\xd0\x7e\x09\x0c\xf7\x46\xdc\xa4\x42\x4e\x92\xe4\x54\x95\xdb\xa9\ -\xf7\xaf\x19\x52\xc2\xf9\x7a\x18\x74\xc5\x3f\xb0\xa4\xac\x8a\x75\ -\x9b\x3e\x67\xfb\x9e\xa3\xbd\x0e\xe6\x88\x8b\x89\xe2\xef\x97\xcc\ -\xe9\x9a\x5c\x5c\x9c\xac\x6e\xec\x9a\xd8\x8d\x01\x2b\x84\x70\xe5\ -\x97\x3b\x9f\x01\xf1\xb2\xaf\x4a\xb2\x06\xb8\x1f\x49\x38\xda\xcf\ -\x27\xd1\xbe\xc0\xa5\xea\x16\x76\x96\x17\xf3\xd1\x8e\xfd\x1c\x3a\ -\x5e\x14\x70\x14\xcd\x3f\xff\x60\x45\xb7\xd1\x2f\xa4\xfc\xcd\x0a\ -\x21\xba\x39\x36\x79\x34\xed\x14\x27\xab\xaf\x0d\xab\xd0\x1e\x03\ -\x26\xf8\xaa\x28\x3d\x4e\x52\xde\xcc\x75\xd3\x4a\xf6\x16\x0e\x9b\ -\x95\x0f\xff\xf2\x32\x76\x87\x9d\xe6\x86\x3a\x1a\xea\x83\xe7\x55\ -\xbd\x78\xde\x34\x6e\x9e\x32\xa6\x6b\x72\xfe\xd9\x14\x75\xbd\xa7\ -\xfc\x1e\x63\x68\x56\x08\xe1\x52\x34\xe5\x11\xf0\xbd\xc2\x08\x60\ -\xdc\x60\x19\x94\x0b\xb5\xfb\x13\x2e\x97\xcb\x6d\x36\x3d\x57\x18\ -\xd4\xce\xcf\x19\x9e\xc1\x8f\x1f\xe8\x16\x04\xae\x21\x95\xc7\x3c\ -\x8d\x7e\xf0\x71\x63\xd6\x84\x34\x71\x10\x78\xad\xa7\x4a\x23\x4d\ -\x90\x39\xa0\x3f\xb5\x31\x37\x26\x86\xa6\x27\xf3\x87\x9f\x3f\xd4\ -\xcd\x60\x2f\x21\xcf\xd7\x45\xae\x3e\x07\xaf\x26\x94\x9f\x02\x3d\ -\x46\xeb\x65\x27\x4a\xaf\x1e\x6f\x7f\x0b\xc8\xce\x4a\x63\xcd\x2f\ -\x1e\x21\x3a\xb2\x5b\xa0\x76\xad\x53\x2a\xff\xe2\xab\xac\x4f\x06\ -\xe4\x26\x8b\x5a\x21\xe5\x4a\x7c\x58\xcc\xc0\x2d\x15\x8d\x1c\xf8\ -\xb7\x39\x0b\xe6\xcd\x18\xcf\x7f\xff\xfa\x31\xe2\xe3\xba\xd9\x4a\ -\xa4\x40\x3e\x38\x2d\x55\xf8\x1c\xc0\x3d\x2e\xdf\x13\x53\x0d\x5b\ -\x85\x60\x4d\x4f\xf9\x32\xe3\x25\x03\x23\xfb\x8f\x09\x46\x9d\xee\ -\xf3\xc1\x46\x58\xa8\x89\x7f\x5c\x75\x17\xbf\x7a\x72\x25\xa1\x21\ -\xdd\xdb\x22\xe1\xff\xf9\xf3\xde\x8c\x5f\x0e\x2e\xae\x0a\xe5\xa7\ -\xca\x60\xd7\x74\x10\x33\xbc\xe5\x11\xb8\x5f\x3a\xfa\xec\x6c\xdf\ -\x4a\x44\x06\x83\x91\x29\xb3\xe6\x33\x6d\xd6\xad\x7d\x5a\x8f\x2f\ -\x4c\x19\x3f\x82\xa7\x1e\x5e\xe1\xfd\xfd\x02\x21\x0f\x86\x36\xa8\ -\x3f\xf7\x87\x96\x5f\x0c\xc8\xcd\x15\x8e\xa3\x95\xf2\xbb\x52\xd3\ -\x0e\x02\x89\xde\xf2\x85\x9b\x20\x3e\x1c\x8f\xde\x70\xc1\xc0\x90\ -\xac\x6c\xe6\x2f\xba\x9b\xf8\x84\x9e\xef\x63\xe8\x0b\x8c\xcd\xce\ -\xe4\xbe\xbb\xe6\x33\x33\x37\xc7\x57\xb6\x1a\x0d\xf5\xdb\xfe\x3e\ -\xf2\xe3\xb7\x8b\xd7\xc4\xc1\xe2\x42\x7e\x85\x5c\x24\xa5\xb6\x53\ -\xe0\x39\xae\x0c\xdc\x8f\x37\x1c\x28\x53\x82\xca\x84\xd8\xf8\x04\ -\xe6\x2d\x5c\xc6\xd0\x11\x3e\x7f\x78\x9f\xc0\x64\x34\x32\x67\xc6\ -\x38\x96\xcc\x9f\xce\xc4\x9c\x61\x3e\xf3\x4a\x68\x91\x42\x59\x98\ -\x9b\x2c\x2e\xfa\x4b\x5f\x97\x8f\xdd\xa4\x64\x71\xf8\xc8\x25\xc7\ -\x52\x84\xf2\x31\x78\x7e\x1d\x2f\xc4\x00\x37\x67\x68\x94\x35\x09\ -\xbe\xa9\x16\x58\x02\x88\xdd\x33\x1a\x4d\x4c\xbb\xe5\x56\x72\x6f\ -\x9a\x87\xc1\xd0\xb7\x51\x98\x1d\x11\x16\x6a\x62\xca\xb8\x11\xcc\ -\x98\x94\xc3\x9c\x19\xe3\x3a\xc5\x3c\xf8\x80\x5d\x15\xda\xf2\x09\ -\xc9\x6a\xbe\x9e\xba\x74\xbf\xa0\x31\x39\xd5\xb8\x33\xbf\xdc\x79\ -\x3f\x88\x37\xf1\xb2\x89\x0b\x01\xe9\xb1\x92\xd4\x18\x49\x55\x8b\ -\xfb\x25\xa5\x56\x9d\x51\x9e\xc3\x47\x8d\x63\xee\x82\xbb\x88\xf6\ -\x62\xc0\x09\x16\x14\x45\x30\x28\x31\x9e\xac\xf4\xc1\xe4\x8c\xc8\ -\x60\x4c\x76\x06\xa3\x87\xa7\x77\x33\x23\xf6\x00\x4d\x22\xbf\x3f\ -\x21\xd9\xf8\x99\xde\xfa\x7b\xf5\x86\xcc\xa4\x14\xc3\x86\x23\x15\ -\x4e\xbb\x90\xe2\x4d\xc0\xeb\x0d\x1d\x8a\x80\xe4\x68\xf7\x13\x54\ -\xf9\x97\x04\xe5\xcd\x3d\x6f\xd0\xf1\x09\x03\x99\xb7\xf0\x6e\x32\ -\x86\x75\x37\xe5\x79\xc3\xed\x4b\xee\xa1\xae\xb6\x9a\x96\xc6\x06\ -\x6c\x36\x0b\x36\x9b\x15\x97\xc3\x81\xfd\x4a\xe4\xa6\x50\x04\x21\ -\x21\x61\x28\x8a\x8a\x29\xc4\x44\x7a\x62\x04\xe3\x33\xa3\x49\x1a\ -\x10\x4b\xea\xe0\x04\xbd\x9d\xdd\x15\x76\x90\x2b\x27\xa7\x18\x7a\ -\x75\xb1\x6a\x40\x22\xcb\x91\x4b\x8e\x79\x42\x28\xef\x03\x3d\x3a\ -\x0c\x79\x7b\xf7\xf1\x2a\xd6\xfe\xf1\x97\x8c\x9d\x34\x9d\x69\x37\ -\xdf\x86\xaa\x33\x48\x5c\x2f\x6e\xc9\xd4\x88\xf7\xef\x72\xab\x9e\ -\xd0\x7a\xe5\x19\x2b\x5d\xef\xc6\x74\x44\xc0\x32\xe3\x91\x0a\x39\ -\x59\x48\xed\x63\xa0\xc7\x98\xa6\xe2\x3a\xc1\xc9\x2a\xcf\x55\x9a\ -\xdb\x5a\x08\x8f\xe8\x9f\x2b\x2e\xe7\x0f\xd3\x88\x0a\xfc\x66\x85\ -\x6a\x29\x94\xc5\x93\x93\xc5\x91\x40\x88\x04\xac\x47\x9b\x9c\x2c\ -\x8e\x38\x51\x72\x41\xee\xeb\x29\x6f\x5c\xa8\xf7\x83\x5a\x7f\x75\ -\x7e\x52\x94\x0c\xbc\xf3\xa5\x3c\xa4\xaa\xca\x8c\x40\x3b\x1f\x82\ -\xf3\x32\x20\x53\x53\x44\x59\x53\xb2\x3a\x5b\xc2\xaf\xf0\xa1\x41\ -\x0d\xed\x3f\x41\xc6\x2b\x46\x26\x06\x74\x5a\x97\xc0\x73\x21\x4d\ -\xea\xac\xf1\x83\x44\x60\x31\x55\x57\x10\xb4\xc5\xf6\xca\xcb\xd3\ -\x4f\xe7\x97\x3b\xf3\x41\xbc\x02\x74\x3b\x26\x06\xf8\xca\x75\xc0\ -\x18\x18\x29\x89\xd3\x1f\xa3\x7d\x15\xb5\x12\xf9\xc0\xe4\x14\xc3\ -\x96\x20\x36\x29\xf8\xaa\xfc\x49\x29\x86\xcd\x06\xa7\x32\x1c\x78\ -\x8e\x2e\xb3\xe1\x7a\xa8\xeb\xe2\xc2\xdc\x0f\x3c\x8f\x1d\x24\xaf\ -\xbc\xed\xa2\x1b\x12\xc9\x7a\x14\x65\x74\xb0\x3b\x1f\xfa\xf8\x49\ -\xf3\x23\x15\x72\xb2\xd0\x5c\x2f\x20\xc4\x14\x70\xbb\x83\x7c\xf4\ -\x8d\xd2\x2f\x8c\x88\x0f\x87\xd1\x49\x1a\x09\x81\x49\x3b\xc7\xae\ -\x3c\x69\xde\xe3\xfe\xd6\x5b\xf4\xe9\xa2\x30\x39\x59\x1c\x29\x4e\ -\x51\x67\x80\x5c\x0d\x9c\x55\x15\x30\xf5\xad\x84\x89\x2a\x20\x37\ -\x55\x72\x4b\x66\x40\x9d\x5f\x24\xa4\x5c\x55\x9c\xac\xe4\xf6\x65\ -\xe7\x43\x1f\xcf\x80\x8e\x90\x52\x2a\xf9\x15\xae\xc5\x3b\xcf\x29\ -\x1b\x5a\xac\x22\x38\x52\xb8\x07\x0c\x4f\x90\xe4\x24\xf5\x6e\x8e\ -\x49\xc9\x49\x81\xfc\x43\x53\x8a\xfa\xd6\x95\x3d\xad\xcf\xd1\xc7\ -\xe3\xf1\x1a\xae\xf8\xc3\x6c\x91\x52\x46\xbe\x59\xa0\x3d\xd9\x64\ -\x16\x8f\xd5\xb6\x89\x8c\x60\x3b\xd6\xf6\xe2\xed\x61\x0b\xf0\x21\ -\x8a\x5c\x37\x69\x90\xfa\xa9\x10\xa2\x5f\xb7\xaa\xeb\xea\xce\xb0\ -\xee\xb0\x4c\xb7\xba\xb4\xdf\xb6\x39\x94\x85\x4d\x16\xe2\x83\x11\ -\xda\x3b\x34\x5e\x32\x6e\x70\x8f\x84\x9c\x08\xb9\x07\xc9\x7a\xcd\ -\xaa\xbe\x97\x9b\x25\x02\xbb\x8e\x25\x00\xdc\x30\xfe\x24\x6f\x15\ -\xca\x04\x6b\x8b\xf6\x90\xc5\xae\x2c\x6b\xb1\xc9\x31\x16\xa7\x08\ -\xed\x8d\x7b\x8e\xb7\x25\x48\x48\xce\x03\xdb\x41\x6e\xc7\xa1\x7e\ -\xde\xd1\x41\xf6\x7a\xe2\x86\x61\x40\x57\xbc\x55\x28\x13\x34\x33\ -\xb7\x3b\x9c\xda\x4d\x36\x97\x32\xde\xe6\x22\xc3\xa5\xc9\x68\x97\ -\x26\x8c\x2e\x0d\x83\x53\xc3\xe0\x74\x21\xa4\x00\xa3\x82\x54\x90\ -\x5a\x88\x51\xd8\x66\x66\xb8\x4e\x98\x54\x71\x01\x41\x91\x40\x9e\ -\xd1\x50\x0b\x2d\x46\x8a\x66\x25\xf6\xfc\xb0\xe6\xf5\xc0\xff\x00\ -\x7f\xe1\xcb\x24\x2c\x98\x3d\x94\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ -\x00\x00\x49\xbb\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x00\x00\x00\x01\x00\x08\x06\x00\x00\x00\x5c\x72\xa8\x66\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ -\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x20\x00\x49\x44\ -\x41\x54\x78\x9c\xec\xbd\x67\x7c\x1c\xd5\xbd\xff\xff\x3e\xb3\xab\ -\xde\x25\xab\x17\xf7\x82\xbb\x64\x19\x1b\x63\x5c\x31\x06\x4c\xb3\ -\x29\x09\x84\x16\x48\xc8\x4d\x0f\xe1\xde\x4b\x92\x9b\xe4\x47\x92\ -\xff\x4d\x42\x72\x13\xd2\x81\x04\xd3\x0c\x04\x0c\x36\x1d\x4c\x71\ -\xc3\x05\x5c\x24\xb9\xf7\xaa\x66\x49\xb6\xba\xb4\x7d\xe7\xfc\x1f\ -\xac\x7b\x91\x76\x56\xb3\x3b\xbb\xd2\xbc\x1f\xf0\xc2\xab\x99\x73\ -\xbe\x5a\xcd\xf9\xcc\x29\xdf\x22\x30\x89\x68\x9e\x5c\x27\xb3\xe2\ -\xe2\x98\x2c\x24\xc5\x4e\x8f\x3a\x58\x95\x4a\xb6\x2a\xc9\x50\x55\ -\x52\x3c\x52\x26\x79\x54\xe2\x3d\x2a\x31\x48\x21\x54\x55\x2a\x2a\ -\x42\x01\xf0\xa8\x08\x55\xfa\xda\x88\xb6\x20\x4f\xb5\x67\x55\xf0\ -\x08\xc0\x6a\xc1\x63\x55\x70\x58\x14\x3a\x15\x21\xdb\x2d\x8a\x68\ -\xe9\x17\xaf\x6e\x1b\x98\x2e\x37\x0a\x95\x3a\x69\xb5\x1c\x71\x78\ -\x38\x3c\xa5\x50\xd8\x0d\xf9\xc5\x4d\x74\x41\x18\x6d\x80\x49\xf7\ -\xfc\x7d\xa5\x4c\x4c\x48\x61\xae\xea\x55\x67\x38\x3d\xa2\xc4\xa5\ -\x8a\x22\x87\x9b\x74\x97\x97\x58\xb7\x17\x25\x54\x76\xc4\x5a\xe1\ -\xea\xa1\x2a\xd6\x73\x7b\xac\x07\x79\x18\xc4\x11\x24\x87\x11\x72\ -\xbb\xc5\x62\xd9\xee\xae\x66\x6f\x69\xa9\x70\x87\xca\x36\x93\xc0\ -\x30\x05\x20\xcc\x78\x66\xa7\x4c\x8f\xb2\xf3\x65\xb7\x2a\xe7\xda\ -\xdc\x8c\xb5\xb9\x44\xae\xdd\x43\x8c\x94\xdd\xdf\x1b\x0a\x46\x64\ -\x49\x46\x64\xfa\x65\x8c\x0b\xd8\x85\x64\xbb\x10\x6c\x53\x85\xb2\ -\xc1\xe9\x65\xb3\x39\x63\x08\x2f\x4c\x01\x30\x98\x97\xca\xe5\x48\ -\x9b\x57\xbd\xcf\xe1\x14\xb3\x1c\x1e\x31\xbc\xd3\x45\x52\x98\x8c\ -\xf5\x8b\x22\x04\x8c\xcc\x92\x0c\xed\x17\x90\x95\x1e\x60\x2b\xb0\ -\x0e\x29\xcb\x54\xc5\xb2\xaa\x34\x4f\x54\xea\x6b\xa1\x89\x16\x4c\ -\x01\x08\x31\x4f\x54\xc8\xd4\x44\x0f\x5f\x77\x7b\xb8\xbd\xc3\x25\ -\x47\x77\x3a\x45\x5c\x38\x0f\xf8\x4b\x31\xa9\x50\x92\x9b\xdc\x73\ -\xcb\x85\xe4\x10\xf0\xa9\x2a\xe4\x7b\xc9\x36\xcb\xc7\x43\x87\x0a\ -\x67\xcf\xad\x33\xf1\x17\x53\x00\x42\xc0\xe2\x32\x39\xa1\xdd\xa3\ -\x3e\xd2\xe6\x56\x66\xb7\xda\xc9\x52\x23\x71\xc4\x9f\xc7\xa8\xec\ -\x80\x67\x01\x5d\xd1\x01\x62\x39\x52\x2e\x53\xa4\xf2\xee\xf8\x42\ -\x51\xa3\x77\x07\x26\xe7\x62\x0a\x40\x90\x78\xaa\x5c\x96\x7a\xec\ -\xf2\x47\x76\x8f\x98\xd3\xe1\x44\x87\x77\x65\x78\x31\x6d\xa0\x4a\ -\x7a\x7c\x50\xbb\x50\x41\x7e\x2e\x84\x78\x5d\x7a\x94\xd7\x4b\x8a\ -\x44\x6d\x50\x7b\xeb\xa3\x98\x02\xa0\x23\x8b\xb6\xca\xe1\x0e\x87\ -\xfc\x55\x9b\x53\xcc\x6d\x75\x90\x6c\xb4\x3d\xc1\xa2\x28\x45\x52\ -\x52\x10\x52\x49\x53\x11\xf2\x33\x54\xb1\xd8\x8d\xb2\x78\x52\x81\ -\x68\x0c\x65\xe7\xbd\x19\x53\x00\x74\x60\x51\xb9\xbc\xf3\x78\x07\ -\xbf\x6d\x73\x50\xa4\x1a\x6d\x4c\x90\x89\xb5\xc2\xec\xa1\x2a\x51\ -\x21\x3b\x7c\xbc\x00\x27\x42\xbc\x83\xf4\xfe\xb3\x38\xcf\xba\x5c\ -\x08\xd1\xdb\x26\x57\x21\xc5\x14\x80\x40\x90\x52\xac\xac\x21\x5f\ -\xb8\xd4\x87\xaa\x5b\xc5\xb7\x0f\x9e\x10\xe9\xde\x3e\xf2\x18\x8e\ -\xcb\x93\x0c\x4c\x0b\x9b\x5f\xf6\x80\x84\x97\xbd\x28\x0b\x2f\xcf\ -\x17\x55\x46\x1b\x13\x89\x98\x02\xa0\x81\xef\x7e\x20\x63\x06\x27\ -\xf3\x13\x87\x9b\xef\x39\xbd\xa4\x7a\x7b\xe9\xeb\x3e\x35\x0e\x06\ -\x65\x48\x92\xa2\x25\x55\x2d\x82\x43\x4d\x67\x1e\x93\xe9\x83\x54\ -\xd2\xe2\x0c\x34\xee\x62\x08\xdc\x42\xe5\x35\x50\xfe\x50\x5c\x20\ -\xb6\x18\x6d\x4e\x24\x61\x0a\x80\x1f\xfc\xf8\x03\x99\xd9\x2f\x89\ -\xdf\xda\x5d\x7c\xc5\xe5\x25\xc6\x68\x7b\x82\x45\xac\x15\x46\x66\ -\x4b\x0a\x53\xe5\xe9\x07\xe3\x68\x8b\xa0\xa2\xc6\xf7\xaf\xc4\x18\ -\x98\x3e\x50\x25\xca\x62\x9c\x8d\xdd\x23\xd7\x49\x78\xbc\x24\xcf\ -\xf2\x9e\xb9\x3c\xe8\x1e\x53\x00\xba\xe0\x37\x2b\xe5\x80\xd8\x28\ -\x16\xb6\xda\x99\xa9\xca\xde\xfb\x5d\x59\x2d\x30\x24\x5d\x32\xa4\ -\x9f\x3c\xc7\xcd\xf7\x48\xb3\x60\x47\x9d\xc0\xa3\x42\x5a\x1c\x5c\ -\xd1\x5f\x25\x3a\xac\x07\xff\x19\xa4\x64\x87\x82\xfc\xbf\x96\x7c\ -\xcb\xcb\x33\x85\xf0\x18\x6d\x4f\xb8\xd2\x6b\x1f\xea\x9e\xf0\xf8\ -\x4a\x59\x10\x65\xe1\x5f\xed\x4e\xe6\x7a\xc3\x74\xe0\x0b\x01\xa9\ -\xb1\x90\x16\x27\xb1\x9c\x1c\xb4\x76\x37\xb4\x38\x04\x1d\x27\x5d\ -\x69\x14\x01\xfd\x12\x24\xf1\xd1\xf8\x36\xed\x24\xb8\x25\xb8\x3d\ -\xa0\x28\x60\x15\xbe\xe9\x7e\x7e\xb2\xc4\x7a\xd6\xc0\x96\x12\xb6\ -\xd4\x0a\x8e\xb6\x9c\xf9\xd5\xaf\x1c\x20\xc9\x4c\x88\xc8\x17\xea\ -\x11\x29\xf8\xcd\xc1\x5c\x65\xe1\x1d\x42\x78\x8d\x36\x26\xdc\x08\ -\xcb\x87\xdb\x28\xfe\xf7\x33\x99\x1b\x27\x78\xba\xcd\x21\x6f\x50\ -\xa5\x08\xdb\xef\x26\x2d\x0e\x4a\xf2\x55\x92\x2e\xb1\x18\xd9\xdf\ -\x28\xd8\x59\x27\xb8\xa2\x48\x92\xad\xd1\xb1\x58\x95\xb0\xb9\x5a\ -\x50\xdb\x76\xee\xaf\x3f\x26\x47\x32\x38\x23\x22\x05\xe0\x14\xbb\ -\x91\xf2\xb7\xc5\xf9\x96\x97\x84\x10\xbd\x74\xf7\x46\x3b\x61\xfb\ -\x90\x87\x92\xc7\xde\x95\xf1\xa9\xa9\xea\x93\xad\x0e\xe5\x6e\xaf\ -\x0c\x5d\x74\x5d\x20\x44\x5b\x60\xce\xb0\xae\x8f\xe1\x3a\x5c\xf0\ -\xe9\x7e\x05\xab\x02\x43\xfb\x49\x86\xf5\x93\xf8\x23\x67\x5e\x09\ -\x9b\x2a\x05\x75\x1d\x17\x5e\x2c\x04\x64\x25\x48\x92\x62\x20\x3e\ -\x1a\x72\x92\x24\xf1\x51\x3d\xf8\x45\x8c\x63\x8b\x22\xd4\x47\xc7\ -\xe7\x45\x7d\x6c\xb4\x21\xe1\x40\x9f\x16\x80\xc7\x1e\x93\x4a\xf2\ -\x2c\x1e\xb1\xbb\x79\xcc\xe9\x25\xb8\x7e\x6d\x3a\x91\x18\x0d\xc3\ -\x32\x7d\xd3\x7e\x8f\xd7\x37\x5d\x8f\xb2\x42\x6e\x92\x44\x39\xeb\ -\xaf\xb9\xff\x84\x60\xdf\x71\x81\x14\x70\x45\xa1\x4a\x46\x42\xd7\ -\xed\x7a\x54\xf8\xe2\xa8\xe0\x84\xcd\xbf\x47\x22\x4a\xf1\xf9\x03\ -\xc4\x5a\x7b\xf0\xcb\x18\x8a\xf8\x14\x45\x7c\xbf\x24\x57\xec\x32\ -\xda\x12\x23\xe9\xb3\x02\xf0\xc7\xd5\xf2\x1a\x97\xca\x0b\x36\x37\ -\x39\x46\xdb\xa2\x07\x85\x29\x92\x09\x01\x7a\xe7\xa9\x12\xbe\xa8\ -\x14\x34\x5c\xe4\xcd\xdf\x15\x7a\x05\x04\x19\x86\xc0\x8d\xe4\x49\ -\xd5\xa1\xfc\xbc\x74\xb0\x68\x35\xda\x1c\x23\xe8\x73\x02\xf0\xeb\ -\x4f\x65\x46\x6c\x0c\x6f\xb6\xda\xb9\x2a\x82\x1f\xdd\x0b\x28\xce\ -\x93\xf4\x0f\xc0\x41\x47\x02\x65\xd5\x82\xea\x56\xed\x8f\xc2\x35\ -\x43\x55\xe2\xa3\x35\xdf\x16\x8e\x34\x48\xe4\x8f\x4a\xf2\x2c\xcf\ -\xf7\xb5\xa3\xc3\xb0\x5e\xef\xea\x8a\x94\xe2\x0f\x6b\xe5\x7f\x7a\ -\x05\xb5\x2d\xbd\x6c\xf0\x0f\x4a\x0f\x6c\xf0\x83\x6f\xa9\x10\xc8\ -\xe0\x4f\x3e\xb9\x17\xd0\x4b\xc8\x12\x88\x67\x2b\x6a\xbd\xab\xb7\ -\xd6\xc8\xe1\x46\x1b\x13\x4a\xfa\xc4\x0c\x60\xa5\x94\xd6\xf2\xb5\ -\xf2\x8b\x56\xbb\x98\x60\xb4\x2d\x7a\x53\x98\x22\x29\xc9\xf7\x6f\ -\x93\xef\x7c\x9c\x1e\x58\x7e\x40\xc1\x15\xc0\xe1\x58\xdc\xc9\xf4\ -\x60\x96\xde\xf7\x0a\xb1\x4b\xf8\x5d\x6c\x8b\xf2\xeb\x51\xa3\x84\ -\xcb\x68\x63\x82\x4d\xaf\x17\x80\xb2\x5a\x39\x41\x48\x75\xe1\xfe\ -\x13\x62\xdc\xce\xfa\xde\xf5\xeb\xc6\x47\xf9\x36\xe2\x2c\x3d\xf8\ -\xb5\x54\xe9\x3b\x35\x70\x7a\x7c\x8d\xb8\xbc\xe0\xf6\x42\xab\x03\ -\xda\x9c\x02\x9b\xd3\x27\x14\x17\x3b\x37\x1b\x9f\x27\x19\x10\x3e\ -\x71\x01\xba\x22\xa1\x42\x0a\xe5\x6b\xa5\x79\xa2\xdc\x68\x5b\x82\ -\x49\xc4\xee\xe1\x76\x87\x94\x52\xd9\x72\x4c\xfd\xae\x94\xea\xef\ -\x80\xe8\xb8\xc8\x3c\xb2\xea\x92\xa2\x54\xd9\xa3\xc1\x0f\x3e\x67\ -\xa1\xe4\x18\x20\xe6\x62\x03\xd9\xf7\x99\x94\xd0\xee\x84\xbd\xc7\ -\x05\x35\x67\xf9\x07\xf4\xd6\x58\x08\x00\x01\xc5\x42\xaa\x1b\xca\ -\x6a\xbc\xff\x7b\x30\x4f\xf9\x55\x6f\x75\x22\xea\x5d\xaf\xc4\x93\ -\x6c\xae\x95\x45\x8a\xf4\x2e\x02\x31\x0d\x7c\x6f\xb5\x0d\x55\x0a\ -\x8d\x9d\x46\x5b\xa6\x2f\xe3\x72\x25\x03\xd3\xbb\x7f\x03\xcb\x93\ -\xff\xe9\xa9\x6b\x93\xc4\xb7\x67\x70\xb8\x49\x90\x18\x2d\x29\x2d\ -\x90\xc4\xf4\xda\x57\xc8\xd9\xc8\xcf\xbd\x56\xcb\xdd\x13\xb3\xc5\ -\x21\xa3\x2d\xd1\x9b\x5e\x27\x00\x2f\x6c\xf2\xfe\xd6\x6a\x15\xdf\ -\x73\x7b\x89\x73\x7a\xa0\xc3\x25\x68\x77\xfa\xa6\xba\xbd\x8d\xdc\ -\x24\xc9\xa4\xa2\xae\x7f\xb1\x83\x8d\x82\xdd\x0d\x82\x91\xd9\x92\ -\x41\x7e\x88\x85\xc9\x25\x69\x93\x42\x7e\x7b\x42\x9e\xf5\x25\xa3\ -\x0d\xd1\x93\x5e\x23\x00\xef\x6d\x93\x69\xd5\x36\xbe\xa8\x6d\x63\ -\x58\x57\xd7\xc5\x58\xc1\x22\xc0\xd6\x4b\x32\xd6\x9f\xbd\x0e\x6f\ -\x77\xfa\x3c\x05\x63\xac\xbe\xb7\xf5\x8e\x3a\xc1\xc1\x46\xdf\x9f\ -\xb8\x30\x55\x32\x21\xdf\x14\x80\x1e\x23\x78\x41\x45\xf9\x56\x69\ -\x9e\xb0\x19\x6d\x8a\x1e\xf4\x0a\x01\xf8\x77\x99\xbc\xea\x58\xa7\ -\x5c\xa6\x28\x22\xbe\x5f\x82\x24\x21\xca\xb7\x6e\xdd\x5e\x27\x38\ -\x95\xa8\x23\x31\x06\x8a\x73\xcf\x78\xc4\x35\xdb\xe1\x70\x93\xa0\ -\xd3\x2d\x48\x39\xb9\xfe\x3d\xdc\x2c\x08\x97\xfc\xfb\xfe\x22\x80\ -\xac\x44\x89\x57\xc2\x89\x4e\x41\x9c\x15\xc6\xe6\x4a\x8e\x36\x73\ -\xda\xa5\xd7\x6a\x81\x2b\xfb\x87\x61\x1c\x7f\xe4\xb2\x4d\xf5\x2a\ -\x0b\x4a\x8b\xc4\x41\xa3\x0d\xe9\x29\x11\x2f\x00\x2f\x6e\xf2\xfe\ -\x8f\x43\x15\x3f\x1f\x99\x25\xa3\xcf\x3e\x97\x6e\xb1\xc3\xaa\x43\ -\x67\xce\xa8\x86\x64\x48\x46\xe7\x74\x3d\xba\xb7\x9f\xf5\xc6\xec\ -\x0d\x08\x01\xfd\x53\x25\xc3\x33\x25\xbd\x71\x13\xd4\x60\xda\x84\ -\x90\xf7\x15\xe7\x59\xdf\x32\xda\x90\x9e\x10\xb1\x4f\xfb\x63\x52\ -\x2a\x39\x1b\xf8\xb0\xdd\x29\xaf\x99\x31\xf8\xcc\x6e\xb8\xcb\xeb\ -\x7b\xb3\x1f\x38\x21\x70\x9f\xb5\x4b\x6d\xb5\xc0\x75\xc3\xbb\x3e\ -\x32\xab\x6a\x15\x94\x55\x87\xdf\x57\x62\x51\xb4\xed\xb8\xa7\xc4\ -\x42\x41\x8a\x2f\xb1\x47\xe4\xfa\xea\x47\x04\x12\xf8\x5d\x71\x9e\ -\xf2\x93\x48\x8d\x30\x0c\xbf\xa7\xdd\x0f\x9e\xde\x2c\x73\xed\x0e\ -\x2a\x9a\xed\x64\x4f\x1f\xa8\x92\x7a\x72\x6a\x5b\xd5\x2a\xd8\x7a\ -\x4c\xe0\xb9\xc8\x81\x4d\x46\x02\x5c\x39\x40\x3d\xc7\xf5\x51\x4a\ -\xa8\xef\x14\x20\x7d\x71\xf5\xdb\xeb\x02\xf3\x8a\xd3\x1b\x21\x7c\ -\xd3\xfa\x82\x64\xc8\x4c\xf4\x0d\xe2\xf2\x1a\x41\x65\xcb\xc5\x6d\ -\x8b\xb1\x42\x46\xbc\xa4\x5f\x02\xe4\x24\xca\xde\xe4\xa1\x17\x21\ -\x88\xb7\x2d\x16\xf1\x95\x71\x39\x22\xe2\xce\x99\x8c\x7f\xda\x35\ -\xf2\xc2\x16\x59\x5c\xdf\x22\xd7\x75\xba\x44\xdc\xd8\xb3\x12\x54\ -\xee\x6e\x10\xec\x3d\x7e\xe9\x5f\xa7\xb4\x40\x52\x90\x72\x66\x09\ -\x20\x25\x6c\xac\x12\x1c\x6b\x0f\x9f\xaf\x20\x4a\x81\xa2\x34\xc9\ -\xa0\x0c\xdf\x3e\x06\x80\xdd\x03\x15\x35\xe7\x06\xea\x08\x7c\x39\ -\x01\xf2\x52\x24\x59\x89\xbe\x10\xdd\xf0\xf9\x2d\xfa\x2c\x5b\x2d\ -\x52\xb9\x61\x5c\x81\xa8\x36\xda\x10\x2d\x44\xd4\x04\xf1\x85\x4d\ -\xf2\xe6\xda\x66\xde\xb0\xbb\x85\x75\x54\xf6\x99\xc1\x5f\xd5\xd2\ -\xf5\xe0\x07\x2e\x48\x65\x75\xa0\x31\x3c\x06\x7f\xb4\x05\xb2\x93\ -\x24\x79\x49\x90\x95\x74\xa1\x63\xcf\xa6\x4a\x85\xa6\xb3\xca\x69\ -\x26\xc7\x40\x71\x7e\xef\xdc\xd0\x73\x79\x3c\xbc\xfa\xf6\x4a\xae\ -\x9d\x31\x91\xac\x8c\x54\xa3\xcd\xd1\xca\x38\xaf\x50\xbf\x28\xaf\ -\x96\xf3\x4a\x0a\xc4\x56\xa3\x8d\xf1\x97\x88\x11\x80\x17\xcb\xe4\ -\xc3\x47\x5b\xf8\x83\xdb\x8b\x18\xda\xef\x4c\x59\x2a\xb7\xea\xdb\ -\xbc\xeb\x8e\x56\x07\x64\x25\xfa\xfe\xbf\xc5\xe1\x9b\x31\x5c\x8a\ -\x7e\x09\x92\x7e\xf1\x10\x1f\x03\xd1\x8a\x6f\x0d\xee\xf2\xc2\x81\ -\x13\x82\x66\x1d\x6a\xdb\x5a\x14\x5f\x42\x8d\xc2\x14\xdf\x54\x5f\ -\xe9\xc2\x7c\xab\x45\x02\x02\x45\xf8\x2a\xf3\x0e\xe9\x17\xe6\x19\ -\x4b\x02\xa4\x6c\xfb\x7e\xfe\xb8\x70\x09\x47\xab\xeb\xb9\x7a\x6a\ -\x89\xd1\xe6\x04\x4a\xbe\x14\xea\x9a\xb2\x5a\xf7\x1d\x13\xf2\xa2\ -\x96\x19\x6d\x8c\x3f\x44\x84\x00\x2c\xdc\xec\xfd\xcb\xe1\x26\xbe\ -\xeb\x95\xbe\xa9\xef\xc8\xac\x33\x53\xf9\x83\x8d\xc2\xaf\x60\x96\ -\x43\x4d\xe2\x74\xc4\x5c\x79\x8d\x72\x49\xc7\xa0\xf8\x68\x5f\xdd\ -\xbb\xd4\xd8\x73\x3d\xe7\xda\x9c\xe0\xf4\xf6\x6c\xc6\xa0\x08\x18\ -\xd2\x4f\x32\x24\x43\xfa\x9d\x5c\xb3\x38\x4f\x72\xa2\xf3\xcc\x5e\ -\x40\x6f\xa3\xa1\xb1\x85\xa7\x5f\x79\x9f\x8f\x56\x6f\x36\xda\x14\ -\x5d\x10\x90\x84\x54\xde\x2e\xab\xf1\xdc\x3b\x21\xdf\xfa\x9a\xd1\ -\xf6\x74\x47\xd8\x3f\x52\x4f\x7d\xa1\x2e\xae\x6c\x16\xb7\x9f\x3a\ -\x9f\x1f\x93\xa3\x9e\x33\x30\xab\x2f\xb1\x31\x76\x3e\x76\xb7\x2f\ -\x4d\x16\xd0\xa5\x60\xd8\x5c\xb0\xfa\x90\x2f\x9d\x56\xac\xd5\x77\ -\x7a\xe0\xf6\x42\x67\x0f\xe3\xc2\xa2\x2d\x30\xa5\xff\x99\x0d\x4b\ -\x7f\x89\x8b\xf2\x39\xf1\xf4\x36\x9c\x2e\x37\x6f\x7c\xf0\x19\xcf\ -\xbf\xf1\x31\x76\x47\xaf\x0b\xba\x8b\x16\x88\x97\xcb\x6b\x3c\x89\ -\x25\xf9\xd6\x85\x46\x1b\xd3\x15\x61\x2d\x00\x7f\x5f\xaf\x7e\x58\ -\xdb\x26\xae\x3d\xf5\xef\x9c\x24\x79\x4e\x41\xca\x56\x87\x2f\x92\ -\xcd\x5f\xb4\x84\xbd\x7a\x54\x6d\x6d\x77\x47\x69\x81\xbc\xe8\xe0\ -\xef\xb4\xd9\xe9\xb4\x39\xe9\xb4\x3b\xb0\x39\x1c\xd8\xed\xbe\x4e\ -\x5d\x1e\x0f\x0e\xe7\x85\xee\x8a\xb1\x31\x51\x44\x5b\x7d\x7f\xb6\ -\xa4\x04\x5f\x83\x71\x71\x31\xa4\x24\x26\x90\x94\x18\x87\xa2\x84\ -\xff\x02\x61\xed\xa6\x9d\xfc\xe5\xf9\x37\xa9\xad\xef\xd5\x25\xfe\ -\x2c\x20\xfe\x55\x56\xeb\x4d\x9a\x90\x67\xf9\x93\xd1\xc6\x5c\x8a\ -\xb0\x15\x80\x27\x3f\x57\x3f\x3d\xd6\x2e\x66\x9f\xfd\x59\xd1\x59\ -\xfb\x42\x52\xfa\x5c\x5d\xb5\xa0\x08\xdf\x71\x59\x66\x02\x24\xc7\ -\xfa\xda\xd8\xa7\xd3\xba\xfe\x6c\xa4\x54\x69\x69\x6e\xa2\xa3\xad\ -\x99\x8e\xb6\x56\xdc\xb6\x56\x8e\x6e\x68\xe1\x44\x53\x2b\x27\x9a\ -\xdb\x38\xde\xd4\x4a\x47\xa7\x9d\x0e\x9b\xce\x1d\x03\xf1\xf1\xb1\ -\xa4\x24\xc6\x93\x92\x94\x48\x46\x5a\x12\xd9\x19\xa9\xf4\x4b\x4f\ -\x21\x27\x33\x9d\xa2\xfc\x4c\x8a\xf2\xb3\x89\x8f\x35\xa6\xb6\x49\ -\x75\xdd\x09\xfe\xbc\x70\x29\x9f\x57\xec\x36\xa4\x7f\x03\x10\x42\ -\xf2\x44\x45\x8d\x37\xa5\x38\xdf\xf2\x0b\xa3\x8d\xb9\x18\x61\x29\ -\x00\x4f\x6f\x50\x57\xd7\xb4\xfa\x22\xf9\xce\x26\x2e\xca\x37\x15\ -\x56\xa5\xef\x68\xec\x78\x67\xd7\x02\x90\x18\xe3\xf3\x84\x8b\xb1\ -\xfa\x62\xe7\xd3\xe2\xcf\xec\xb2\xdb\xdd\xb0\xa3\xbe\x67\x83\xdf\ -\xeb\xf5\xd2\xd2\x74\x9c\x13\x0d\x75\xb4\x36\x9f\xe0\x44\x43\x1d\ -\x8d\xc7\xeb\x68\x3a\x5e\x8f\xdb\x6d\xcc\xb4\xd6\x66\x73\x60\xb3\ -\x39\x38\xd6\xd0\x74\xd1\x9f\x5f\x3f\xf3\x72\x7e\xf2\xed\x3b\x43\ -\x6a\x93\xd3\xe5\xe6\xe5\xb7\x96\xf3\xd2\x9b\x2b\x70\xb9\x7b\x49\ -\x10\x86\x06\x24\x3c\x56\x5e\xe3\x55\x4a\xf2\x2d\xff\xcf\x68\x5b\ -\xce\x27\xec\x04\xe0\xa9\xcf\xd5\x4f\x2f\x36\xf8\x01\x76\xd4\x2b\ -\x14\x24\x4b\x0e\x37\x0b\xda\x1c\xdd\xb7\x35\x22\xf3\xc2\xb3\xff\ -\xfa\x76\x41\x65\x0b\xd4\xb5\x9f\x89\x13\xf0\x97\x96\xe6\x46\x6a\ -\x8f\x1e\xa2\xae\xb6\x8a\x9a\xaa\x43\x34\x1c\xab\x46\x46\x5a\xf0\ -\x40\x88\x59\xb7\x79\x27\x7f\x7a\x76\xe9\x25\x05\xa9\x0f\xf1\xf3\ -\x8a\x5a\xaf\xa3\x38\xcf\xf2\x1b\xa3\x0d\x39\x9b\xb0\x12\x80\xa7\ -\x36\xc8\x77\x6b\x5b\x99\x7d\xa9\x9f\x37\x76\x42\x63\x37\x6f\xfd\ -\xb3\x39\xdc\x24\x90\xf8\x66\x0c\xad\x76\xa8\x6d\x13\x38\xfc\x2c\ -\x12\xe5\xf5\x7a\xa9\xad\x3a\x44\xd5\xe1\x83\x1c\xab\x39\x42\x6d\ -\xd5\x11\x9c\x0e\xfd\xa7\xec\xbd\x95\xca\xda\x06\x9e\x58\xb8\x84\ -\x4d\x5b\xf7\x19\x6d\x4a\xd8\x20\x25\xbf\x2e\xaf\xf6\xba\x4a\x0a\ -\x2c\x7f\x30\xda\x96\x53\x84\x8d\x00\x3c\xb9\x5e\xbe\x5e\xdb\xca\ -\x0d\x7a\xb6\xd9\x68\x83\x46\x3f\xf3\xdc\x03\xb4\xb5\x34\x71\x70\ -\xef\x4e\x8e\x1c\xd8\x4d\xd5\x91\x03\xb8\x5c\x4e\x3d\xcd\xe9\x13\ -\xd8\x1c\x4e\x5e\x78\xe3\x13\x16\xbf\xb7\x0a\xf7\xc5\x7c\xb2\xfb\ -\x3a\x82\xdf\x57\xd4\x7a\xda\x8a\xf3\xac\xff\x32\xda\x14\x08\x13\ -\x01\xf8\xe7\x06\xef\x33\xd5\xad\xdc\x66\x44\xdf\x4d\xc7\xeb\xd9\ -\xb7\x6b\x2b\xfb\xf7\x6c\xa3\xbe\xd6\x2c\x31\xdf\x13\x96\xaf\xdb\ -\xc2\xdf\x5f\x7c\x9b\x86\xc6\x16\xa3\x4d\x09\x67\x84\x94\xe2\xc9\ -\xb2\x1a\x4f\x5b\x38\xf8\x09\x18\x2e\x00\xcf\x6e\xf2\xfe\xf7\xd1\ -\x16\xe5\xc1\x50\xf6\xe9\xb0\x75\xb2\x6f\xf7\x56\x76\x6e\xd9\x44\ -\x4d\x65\xaf\xcb\xf2\x14\x72\x2a\x6b\x1b\xf8\xd3\xb3\x4b\xd9\xb8\ -\x65\xaf\xd1\xa6\x44\x0a\x16\x21\xc4\xa2\xf2\x1a\x77\x63\x49\x7e\ -\xd4\xa7\x46\x1a\x62\xa8\x00\xbc\x50\x26\xe7\x57\xb6\xf0\xdb\x50\ -\xec\xa3\x49\x29\x39\x72\x60\x37\x5b\x36\xae\xe5\xd0\xfe\xdd\x48\ -\x19\x91\xd1\x9b\x61\x85\xcd\xe6\xe0\xd9\xd7\x3f\xe2\x8d\x0f\xd6\ -\xe0\xf1\x9a\xd3\x7d\x4d\x48\xa2\x40\x59\xb2\xb9\x4a\x4e\x2d\x2d\ -\x14\xdb\x8d\x32\xc3\x30\x01\x78\xb9\x42\x8e\xaf\x6c\x61\xb1\xdb\ -\x1b\xdc\x40\x36\xa7\xc3\xce\xb6\xb2\xf5\x6c\xdd\xb4\x8e\x96\xe6\ -\x5e\xed\x78\x12\x32\xa4\x94\x7c\xb2\xa6\x9c\xbf\xbf\xf8\x0e\x8d\ -\x2d\x6d\x46\x9b\x13\xc9\x24\x2b\x8a\xfa\xce\xf6\x7a\x39\x79\x4c\ -\xb6\xa8\x37\xc2\x00\x43\x04\xe0\xc5\xad\x32\xab\xa6\x45\xae\xb3\ -\xbb\x45\xd0\xfa\xb7\x75\xb6\xb3\x65\xd3\x5a\xca\x3e\x5f\x6d\xee\ -\xde\xeb\x48\x65\x4d\x3d\x4f\x3c\xbb\xd4\xdc\xdd\xd7\x8f\x01\x6e\ -\x8f\xfa\xfe\xd6\x3a\x39\xdd\x88\x7c\x02\x21\x17\x80\xc5\x3b\x65\ -\x74\x4d\x13\xdb\x3b\x9c\x22\x28\xd5\x78\x3b\xda\x5a\xf9\xfc\xb3\ -\x8f\xd8\x51\xbe\x01\xaf\xd7\xcf\x33\x3f\x93\x6e\xe9\xb0\xd9\x59\ -\xf8\xea\x32\x96\x7e\xb4\x16\x6f\x6f\x2e\x08\x60\x0c\x13\x3e\x3f\ -\xc2\xf6\x95\x2b\xe5\xb0\x99\x33\x45\x48\x1f\xda\x90\x0b\x40\x43\ -\xab\x5c\xd3\x6c\x17\x59\x7a\xb7\xeb\x74\xd8\xd8\xb0\x66\x39\xe5\ -\x1b\x56\xe3\xe9\x83\xde\x66\xc1\x42\x4a\xc9\x47\x9f\x95\xf1\x8f\ -\x45\xef\xd0\xd4\xd2\x6e\xb4\x39\xbd\x96\x13\x9d\x0c\x94\xf1\x7c\ -\x0a\xcc\x08\x65\xbf\x21\x15\x80\x85\x1b\xbd\xbf\x39\xda\x22\x2e\ -\xd7\xb3\x4d\x55\x55\xd9\xb2\x69\x0d\xeb\x56\x2c\xc3\xe9\xe8\x15\ -\x99\x9a\xc3\x86\x7d\x87\xaa\xf9\xe3\x33\x4b\xd8\xb1\xef\x88\xd1\ -\xa6\xf4\x7a\x06\x67\x48\xf6\x34\x88\xe9\x0b\x37\x7b\x7f\xf9\x60\ -\xa9\xe5\xe7\xa1\xea\x37\x64\x02\xb0\x68\x93\x9c\x73\xa4\x8d\x47\ -\xf5\x6c\xb3\xae\xa6\x92\x4f\xdf\x7f\x9d\xba\x9a\x4a\x3d\x9b\xed\ -\xf3\x9c\x9a\xee\x2f\x59\xb6\x16\x55\x35\xa7\xfb\xa1\x60\x70\x86\ -\x64\xff\x09\x41\x6d\xab\xf2\xd3\x17\xcb\xe4\xaa\x7b\x27\x88\x15\ -\xa1\xe8\x37\x24\x02\xf0\xca\x5e\xd9\xaf\xba\x8e\x77\xf4\xda\xf1\ -\x77\x39\x1d\xac\x5c\xf6\x26\xdb\x2b\x36\x10\x71\x89\xfc\xc3\x18\ -\x55\x95\xbc\xbf\x62\x03\x4f\xbd\xf2\x1e\xad\x6d\x11\x97\xdf\x32\ -\xa2\xb1\x2a\xbe\xe4\xae\x9d\x2e\x44\x7d\x87\x7c\xef\xcd\x0a\x99\ -\x37\xbf\x58\x04\xdd\xa3\x2a\x24\x02\xd0\xd2\xc4\xe7\x1d\x4e\x62\ -\xf5\x68\xab\xf2\xf0\x3e\x96\xbd\xf9\x0a\x6d\xad\xcd\x7a\x34\x67\ -\x72\x16\x7f\x7b\xf1\x6d\x16\xbf\xb7\xda\x68\x33\xfa\x2c\xee\x93\ -\xae\x14\xed\x4e\x11\xd7\xe0\x94\x6b\x81\xd1\xc1\xee\x33\xe8\xd9\ -\x23\x9e\xd9\xe4\x7d\xaa\xae\x9d\x21\x3d\x6d\xc7\xeb\xf5\xb0\xe2\ -\x83\xa5\x2c\x7e\xe1\x1f\xe6\xe0\x0f\x12\x1d\x9d\xe6\x71\xa9\x91\ -\xe4\x26\xfb\x66\xb3\x42\x40\x8b\x43\x8c\x5a\xba\x4b\xfe\x57\xb0\ -\xfb\x0c\xea\x0c\xe0\xa9\x8d\x72\x76\x6d\x2b\x0f\xf5\xb4\x9d\xf6\ -\xd6\x16\xde\x5d\xfc\x1c\xb5\xd5\x47\x74\xb0\xca\xc4\x24\x3c\x29\ -\xce\x93\x8c\xcb\xf5\x89\xc0\xc9\x44\xb1\x3f\x29\xaf\x95\x8b\x4b\ -\xf2\xc4\xd1\x60\xf5\x19\xb4\x19\xc0\xe2\x9d\x32\xba\xcd\xce\x9b\ -\x1e\xb5\x67\xeb\xfe\xaa\x23\xfb\x59\xf4\xf4\xff\x99\x83\xdf\xa4\ -\x4f\xa0\x08\xce\xce\x12\x9d\x8a\xf4\x2e\x5a\x2c\xa5\x9f\x29\x64\ -\x03\xe8\x2f\x58\x0d\x37\xb7\xcb\xf7\x3b\x9c\x24\xf5\xa4\x8d\xb2\ -\xcf\x57\xb1\xf8\xf9\xbf\x63\xeb\x34\xcf\x9f\x4d\xfa\x2a\xe2\xaa\ -\x21\xc7\xd4\xa0\x2d\x05\x82\x22\x00\x2f\x6d\x95\xb7\x1d\x6b\x17\ -\x57\x07\xdc\x80\x94\xac\x5f\xf5\x21\x2b\x97\xbd\x69\x66\xdc\x31\ -\x31\x81\x5f\x6e\xa9\x91\xc5\xc1\x68\x58\x77\x01\xf8\xcb\x7e\x19\ -\x53\xd7\xca\xf3\x97\xca\xbb\xdf\x1d\x5e\xaf\x87\x77\xdf\x78\x81\ -\xf5\x2b\x23\xa2\xae\x82\x89\x49\xf0\x91\x44\xd5\xb6\xab\xaf\xff\ -\x7d\xa5\x4c\xd4\xbb\x69\xdd\x05\x20\xae\x99\xb7\xdb\x1c\x24\x04\ -\x72\xaf\xd7\xeb\xe5\xdd\xc5\xcf\xb1\x77\x47\x85\xde\x66\x99\x98\ -\x44\x34\x07\x1a\x95\xc1\x4a\x2c\x4b\xf5\x6e\x57\x57\x01\x58\x54\ -\x26\xaf\xad\x69\x65\x6e\x20\xf7\x9e\x1a\xfc\x07\xf6\xec\xd0\xd3\ -\x24\x13\x93\x88\xa7\xc5\xee\xcb\x87\x59\xdf\xc1\x9c\x85\x9b\xe4\ -\x75\x7a\xb6\xad\x9b\x00\x2c\x5e\x2c\x2d\xf5\x1d\xbc\x1a\xc8\xd4\ -\x5f\x3d\x3d\xf8\x0d\xcb\x8b\x60\x62\x12\x96\x48\x60\x7b\xbd\x72\ -\x3a\xb9\x6d\xb3\x9d\x7f\x2f\x5e\xac\xdf\xa9\x80\x6e\x02\xd0\x31\ -\x90\x7f\xb5\x39\x49\x09\xe4\xde\x15\xcb\x96\x9a\x83\xdf\xc4\xe4\ -\x22\xec\xa8\x13\x34\x9e\xe5\x95\xdd\xe6\x20\xa5\xb5\x3f\xba\x95\ -\x1b\xd3\x45\x00\x16\x6f\x91\xf9\xc7\xda\xb9\x2f\x90\x7b\x37\xac\ -\xf9\x84\x2d\x1b\xd7\xea\x61\x86\x89\x49\xaf\xc1\xe1\x81\x4d\xd5\ -\x82\x83\x8d\x17\xba\xd1\xd4\xb7\x73\xcf\x33\xeb\x64\x7f\x3d\xfa\ -\xd1\xc5\x13\xb0\xc5\x21\x97\x3a\x3d\x42\xb3\x98\xec\xd9\x51\xc1\ -\x9a\xe5\xef\xeb\x61\x82\x89\x49\xaf\xa0\xcd\xc9\xc9\xa8\xc0\x4b\ -\x17\xae\x71\xa9\x28\x4e\x85\xa5\xc0\x84\x9e\xf6\xd7\xe3\x19\xc0\ -\xbf\xcb\xe5\xec\xda\x76\xed\x31\xfe\x8d\xc7\xeb\xf8\xe8\xad\x57\ -\xcc\x68\x3e\x13\x13\xc0\xe3\x85\xb2\x1a\xc1\xca\x03\x0a\x55\x2d\ -\xdd\x57\xad\x6a\xe8\xa0\xe4\xb9\x32\x79\x63\x4f\xfb\xed\xf1\x0c\ -\xa0\xc9\xce\x22\xad\x1b\x7f\x6e\x97\x8b\xb7\x5f\x7b\xd6\xb0\xfa\ -\x79\xc1\xc6\x6a\xb1\x90\x96\x92\x48\xbf\xf4\x14\x12\xe2\x7d\x41\ -\x90\x2e\x97\x87\x6d\x7b\xcc\x14\xe4\x26\x17\xe2\x51\x61\xed\x51\ -\x85\x16\x0d\xb1\x58\x52\x42\x73\x27\x0b\x81\x1e\x65\xd7\xea\x91\ -\x00\xbc\xb8\xc9\xfb\xf0\xc1\x66\x72\xb5\xde\xf7\xc9\xfb\x8b\x69\ -\x3a\x6e\x48\x12\x54\xdd\x89\x8b\x8b\xa1\x64\xd4\x10\x86\x0f\x2a\ -\x64\xd8\xc0\x02\x86\x0c\xc8\x25\xbb\x5f\x1a\x42\x9c\xbb\x76\xab\ -\xad\x6f\xe4\x8e\x6f\xff\x7f\x06\x59\x69\x12\xce\xec\xa8\x13\x9a\ -\x06\xff\x29\x5a\x1c\x64\x3e\xb7\xc9\xfb\x93\xaf\x4e\xb4\xfc\x3a\ -\xd0\xbe\x03\x16\x80\xc5\x8b\xa5\xe5\xa8\x5d\xfe\xaf\xd6\x09\xfc\ -\xee\x6d\x9b\xd9\xb5\x65\x53\xa0\xdd\x86\x05\x39\x99\xe9\xcc\xbc\ -\x62\x1c\x57\x94\x5c\xc6\x98\x11\x83\x88\xb2\x06\x2d\x56\xc3\xa4\ -\x97\xd3\x6c\x87\xa3\x2d\x81\xc7\xcb\x1d\xb7\x89\x9f\x2e\x5e\x2c\ -\x1f\xbf\xe3\x0e\x11\x50\x61\x86\x80\x05\xa0\x63\xa0\xfa\xfb\xf6\ -\x66\x25\x4e\xcb\x3d\x0e\x5b\x27\x2b\x97\xbd\x15\x68\x97\x86\x12\ -\x6d\xb5\x32\x75\xe2\x28\xe6\x4e\x9f\xc8\x15\x25\x97\xa1\x28\x41\ -\x4f\xa5\x60\xd2\xcb\x91\x12\xb6\x1e\x53\x7a\xb4\x0d\xd6\xe1\x14\ -\x71\x2d\x03\xd4\x3f\x00\x3f\x08\xe4\xfe\x80\x04\xe0\x31\x29\xad\ -\x8d\x9f\xf1\x4d\xad\xf7\x2d\xff\x70\x69\xc4\x45\xf6\xc5\xc5\x46\ -\x73\xc3\xac\xc9\xdc\xbd\x60\x36\x19\xa9\xc9\x46\x9b\x63\xd2\x8b\ -\xa8\x6c\x09\x6c\xea\x7f\x3e\x8d\x1d\xca\x37\x17\xef\x94\xff\x7d\ -\xc7\x28\xa1\x79\x53\x2d\x20\x01\x18\x54\xae\xfe\xfe\x80\x53\xd1\ -\x94\xe2\xeb\xf0\xfe\xdd\xec\xde\xb6\x39\x90\xee\x0c\x21\x36\x26\ -\x9a\x3b\x6e\x98\xc6\x97\xe6\xcd\x20\x25\x39\xa0\xd0\x06\x13\x93\ -\x4b\x22\x81\x03\x17\x39\xe3\x0f\x04\xbb\x87\xe8\xd6\x4e\xfe\x04\ -\x7c\x4b\xeb\xbd\x9a\xe7\xb1\x8b\xa5\xb4\x34\x74\x88\xff\xd0\x72\ -\x8f\xea\xf5\xb2\xe2\xc3\x25\x5a\xbb\x32\x8c\x29\x13\x46\xb2\xe8\ -\x89\x47\x79\xe8\xce\x79\xe6\xe0\x37\x09\x0a\x76\x17\xb4\xeb\x58\ -\x7d\xbe\xd1\x26\xbf\xf6\xdc\x61\xa9\x39\xef\xa6\xe6\x19\x80\x7b\ -\x8b\xfa\xfb\x36\x8d\x6f\xff\xad\x65\xeb\x69\x6e\x3c\xae\xb5\xab\ -\x90\x93\x95\x91\xca\x0f\x1e\x58\xc0\xb4\x49\x63\x8c\x36\xc5\xa4\ -\x97\x63\xd7\xb9\x76\x8d\xcd\x25\xa2\x3c\xc7\xd5\x3f\x03\xdf\xd0\ -\x72\x9f\xa6\x19\x80\x94\x52\x34\x74\x28\x9a\x3a\x70\xb9\x9c\x6c\ -\xf8\xec\x23\x2d\xb7\x18\x42\xc9\xe8\xa1\x2c\xfc\xdd\x0f\xcd\xc1\ -\x6f\x12\x12\xa2\x82\x90\x8d\xb3\xc9\xa6\xdc\xa7\x35\x50\x48\x93\ -\x19\x2f\x95\xab\x3f\x6e\xb6\x2b\x7e\xd7\xf4\x53\x04\x1c\xa9\x58\ -\x4e\x47\x7b\xf8\x6e\xfc\x29\x8a\xc2\x83\x5f\x9a\xcb\x3d\xf3\xe7\ -\xa0\x28\x41\x2d\x54\x6c\x62\x72\x9a\xa6\x4e\xfd\x9f\xb5\x4e\x17\ -\x31\x9d\x03\xd5\x9f\x02\xbf\xf0\xf7\x1e\x4d\x02\x90\x9d\x28\xe6\ -\x67\x24\x48\x6c\x6e\x68\x73\xc0\x09\x9b\xa0\xe3\x12\xeb\x18\xab\ -\x05\xc6\x64\xd8\xf8\xeb\xca\x55\x5a\xba\x08\x29\xf1\xf1\xb1\xfc\ -\xef\x23\x5f\x65\xe2\xb8\x61\x46\x9b\x62\xd2\x87\x70\x7a\x60\x67\ -\x7d\x70\x5e\x36\x8d\x76\xe5\xfb\x04\x43\x00\xca\xab\xe5\x38\x84\ -\x7a\x5e\xf0\x81\xc4\xe6\x82\x23\xcd\x82\xa3\x2d\x02\xa7\xc7\x97\ -\xd3\x3c\x33\x41\x32\x26\x47\xf2\xce\x87\xeb\xb0\xdb\x75\xdc\xe9\ -\xd0\x91\xa4\x84\x78\xfe\xef\xa7\x5f\x67\xd4\xd0\x01\x46\x9b\x62\ -\xd2\x87\xf0\xaa\xb0\xb1\x5a\xc1\x1d\xa4\x8a\x6b\x6d\x0e\xd2\x9e\ -\x2f\x93\x5f\xb9\x7f\x82\x78\xd9\x9f\xeb\xfd\x9f\x01\x08\xf5\xbf\ -\xe1\xc2\x14\xdf\xf1\xd1\x30\x32\x5b\x72\x59\x96\xc4\xee\xf1\x95\ -\x38\x8a\xb6\x80\xc7\xeb\x65\xc9\xb2\xf0\x0c\xf3\x4d\x4f\x4d\xe2\ -\x89\x9f\xfd\x07\x83\xfb\xe7\x19\x6d\x8a\x49\x1f\xc2\xa3\xc2\xc6\ -\xaa\x73\xe3\xfb\x83\x41\x9b\x93\x5f\x01\x7e\x09\x80\x5f\x9b\x80\ -\x1b\x1b\x64\x0e\x82\xdb\xbb\xba\x46\x08\x88\x8f\xf2\x0d\x7e\x80\ -\xe5\x6b\x2b\x68\x38\x11\xf4\xd2\x66\x9a\x49\x4f\x4d\xe2\xef\xbf\ -\xfa\xae\x39\xf8\x4d\x42\x4a\xab\x03\x56\x1d\x52\x68\xe8\x08\xfe\ -\x3e\x53\x53\x27\x03\x9f\xdf\x22\x47\xf9\x73\xad\x5f\x33\x80\x28\ -\x8f\xfa\x55\x29\x89\xd2\x62\xc4\x1b\x1f\xae\xd1\x72\x79\x48\x88\ -\x89\x8e\xe2\x37\x8f\x3e\x40\x61\x6e\xa6\xd1\xa6\x98\xf4\x11\x1c\ -\x1e\xd8\x77\x5c\x70\xa4\x59\x10\x68\xa6\x6c\xad\xa8\x12\x9c\x4e\ -\xf9\x7f\x40\xb7\xf9\x03\xbb\x9d\x01\x48\x29\x85\x94\x3c\xa0\xc5\ -\x80\xa3\xd5\xf5\xec\x3e\x10\x5e\x25\xbb\x85\x10\xfc\xe8\x9b\x5f\ -\x36\xd7\xfc\x26\x41\x47\x05\xea\xda\x05\x65\x35\x82\x4f\xf7\x2b\ -\x1c\x6a\x0a\xdd\xe0\x3f\x45\x93\x4d\xcc\x7e\x6c\xa5\xec\xf6\x05\ -\xdf\xed\x05\x15\x35\x9e\x59\x08\x45\x53\x71\xcf\x0f\x56\x85\x5f\ -\xb4\xdf\x03\x77\xcc\x65\xce\x55\x25\x46\x9b\x61\xd2\xcb\xb1\xbb\ -\x61\xfd\x51\x45\x57\x2f\xbf\x00\xed\x88\x2a\x48\xe7\x11\xe0\xf1\ -\xae\xae\xeb\x7e\x0f\x40\x28\x5f\xd7\xd2\xb1\xaa\xaa\x7c\xfc\x59\ -\x78\xf9\xfc\x97\x8c\x1e\xca\xfd\xb7\x5d\x63\xb4\x19\x26\x7e\x90\ -\x94\x10\x4f\x6c\x4c\xb4\xd1\x66\x04\x84\x04\x2a\x6a\x85\xe1\x83\ -\xff\x14\x36\x87\xec\x36\x36\xa0\xcb\x19\xc0\x86\x6a\x99\x01\xea\ -\x2d\x5a\x3a\x2d\xdb\x7e\x80\xe3\x4d\xad\x5a\x6e\x09\x2a\x71\x71\ -\x31\xfc\xf8\x5b\x5f\xbe\x20\x41\x87\x49\x78\xa1\x28\x82\xeb\x66\ -\x5c\xce\x37\xef\xbe\x81\xd4\x64\xdd\x0b\xe0\x9c\xc6\xe6\x70\xd2\ -\x70\xbc\x99\xa6\xd6\x76\x4e\x34\xb5\xd1\x61\x77\x60\xb7\x3b\x70\ -\x38\xdd\xb8\xdc\x1e\x84\x80\xa4\x84\x38\x14\x45\x21\x25\x29\x81\ -\x94\xa4\x04\xd2\x52\x12\x29\xc8\xcd\x24\x39\xb1\x6b\x1f\xb8\xa3\ -\xcd\x22\x24\x9b\x7c\xfe\xd2\x62\x17\x45\x2f\x6e\x90\x03\xef\x9d\ -\x24\x0e\x5f\xea\x9a\x2e\x05\xc0\x2a\xd4\x3b\x81\x18\x2d\x9d\xae\ -\xdd\x1c\x5e\xe9\xbd\xbf\x7d\xcf\x4d\xe4\x66\xa5\x1b\x6d\x86\x49\ -\x17\x0c\x1f\x5c\xc8\x23\x5f\xbb\x95\x91\x43\x75\x49\x74\x0b\x80\ -\xcb\xe3\xe1\xd0\xd1\x5a\x0e\x57\xd5\x73\xb8\xba\x8e\x23\x95\x75\ -\x1c\xa9\xa9\xe7\x58\x43\x53\xc0\xf5\x26\x93\x93\x12\xe8\x9f\x97\ -\xc5\x65\x43\x0a\x19\x3d\x7c\x20\xa3\x87\x0f\x20\x2b\x23\x15\x80\ -\xfa\x0e\xc1\x8e\xba\xf0\x19\xfc\x70\x72\x33\x50\xa8\xbf\x02\xee\ -\xbe\xd4\x35\x5d\x0a\x80\x40\xde\x7e\x91\xa3\xff\x2e\xf9\xbc\x7c\ -\xb7\xa6\xeb\x83\xc9\x84\x31\x43\xb9\x79\xce\x15\x46\x9b\x61\x72\ -\x09\x92\x93\x12\xf8\xc6\x9d\xd7\x73\xe3\xd5\x57\xf4\xd8\x0d\xdb\ -\xe3\xf5\xb2\x6b\xff\x51\x2a\x76\x1e\xa0\x7c\xc7\x01\x76\xec\x3d\ -\x82\xd3\xa5\x6f\xc4\x4d\x5b\x7b\x27\xdb\xf7\x1e\x66\xfb\xde\xc3\ -\x2c\x7e\xff\x33\x00\x06\x15\xe5\x32\x66\xcc\x18\x92\x0b\xc6\x90\ -\x9d\x57\xa4\x6b\x7f\x7a\xd0\x6a\x57\x6e\xe8\xea\xe7\x97\x14\x80\ -\xcd\x47\x64\x2e\xa8\x57\x6a\xe9\xec\x68\x75\x3d\xb5\xf5\x8d\x5a\ -\x6e\x09\x1a\x8a\x22\xf8\xfe\x57\xe7\x9b\x53\xff\x30\x44\x51\x04\ -\x37\xce\xbe\x82\x6f\xdc\x75\x3d\xc9\x49\x81\x87\x5b\x77\xd8\xec\ -\x7c\xb6\x61\x3b\xab\x3e\xdf\x4a\xc5\xae\x03\xd8\x1d\xa1\x4f\x32\ -\x7b\xa8\xf2\x18\x87\x2a\x8f\x01\x1f\x93\x9e\x99\xcd\xf8\x89\x57\ -\x32\x6a\xdc\xe5\xc4\xc4\x6a\x4a\x96\x15\x34\xda\x9d\xa4\x3c\x5f\ -\x26\x27\xdc\x3f\x41\x94\x5d\xec\xe7\x97\x14\x00\x4b\xb4\x7a\x87\ -\x94\x68\x8a\x2c\x5a\x5f\xbe\x4b\xab\x7d\x41\x63\xf6\x94\x62\x06\ -\x15\x69\xce\x57\x6a\x12\x64\x46\x0e\xed\xcf\x23\x5f\xbb\x95\xe1\ -\x83\x0b\x03\xba\xdf\x66\x77\xb2\x66\xe3\x76\x96\xaf\xaf\x60\xd3\ -\xd6\xbd\xb8\x3d\x01\xa5\xc2\x0b\x0a\x4d\xc7\xeb\x59\xf1\xc1\x52\ -\xd6\x7c\xf2\x1e\xe3\x4a\xa7\x30\x69\xda\x1c\xe2\xe2\x83\xb7\x9f\ -\xe1\x0f\x12\x70\xb9\xe5\x8f\x81\xdb\x2e\xf6\xf3\x4b\x0a\x80\x94\ -\xf2\x0e\xad\xd3\xff\x0d\x5b\xf6\x68\xba\x3e\x58\x58\x2c\x0a\x5f\ -\xbd\x23\xa0\x1a\xa5\x26\x41\x22\x35\x39\x91\x6f\xde\x7d\x03\xd7\ -\xcd\xb8\x3c\xa0\xe9\xfe\xd1\xea\x7a\x96\x2c\x5b\xcb\x87\xab\x37\ -\x85\x6d\x7c\xc9\x29\xdc\x6e\x17\x9b\x3f\x5f\xc5\xb6\xb2\xcf\x29\ -\x99\x3c\x83\xcb\xaf\x9a\x4d\x74\xb4\xa6\xad\x34\x5d\x69\x77\x89\ -\xab\x2f\xf5\xb3\x8b\x0a\xc0\xd6\x6a\x59\xe0\x45\x9d\xac\xa5\x13\ -\x55\x55\xd9\xb5\xef\xa8\x56\xdb\x82\xc2\xdc\x69\xa5\x14\xe5\xf5\ -\x28\x5d\xba\x89\x4e\x28\x8a\xc2\xfc\xb9\x53\xf8\xda\x97\xaf\x23\ -\x29\xc1\xef\x48\xf2\xd3\x6c\xa8\xd8\xc3\x2b\xef\xac\xa0\x7c\xc7\ -\x81\x80\x37\xef\x8c\xc2\xe5\x72\xf2\xc5\x67\x1f\xb1\x7b\xdb\x26\ -\xe6\xdc\xf8\x25\x06\x0c\x19\x61\x88\x1d\x6d\x0e\x52\x5e\xdc\x20\ -\x4b\xef\x9d\x24\x2e\x38\x9f\xbf\xa8\x00\xa8\x42\xbd\x09\x8d\xc9\ -\x42\x0e\x55\x1d\xc3\xe6\x30\x5e\x99\x15\x45\x70\xdf\x6d\x73\x8c\ -\x36\xc3\x04\x18\x3b\x62\x10\x0f\x7f\x6d\x01\x43\x07\xe4\x6b\xbe\ -\x77\xdb\x9e\x43\x3c\xfd\xf2\x07\x6c\xdd\x7d\x30\x08\x96\x85\x96\ -\xd6\x96\x26\xde\x58\xf4\x24\xa3\x8a\x27\x31\xfb\xfa\x5b\x43\x3e\ -\x1b\x90\x80\x03\xf5\x87\xc0\x5d\xe7\xff\xec\xa2\x02\x20\x11\xd7\ -\xfa\x6e\xf3\x9f\x1d\x7b\xc3\xe3\xed\x3f\x69\xdc\x08\xf2\xb3\xfb\ -\x19\x6d\x46\x9f\x26\x3d\x2d\x99\x6f\xdd\x7d\x23\x73\xa7\x4d\xd0\ -\xbc\x09\x5b\x5b\xdf\xc8\x5f\x9f\x7f\x8b\x35\x9b\x76\x04\xc9\x3a\ -\xe3\xd8\x59\xb1\x81\xfa\x9a\x4a\x6e\xbe\xf3\x6b\xa4\xa5\x87\xf6\ -\x19\xb5\xb9\x94\x99\x17\xfb\xfc\x02\x01\xd8\xb9\x53\x46\x3b\x51\ -\x2f\x7a\x71\x57\xec\xdc\x77\x24\x00\xb3\xf4\x67\xfe\x75\x53\x8d\ -\x36\xa1\xcf\x62\xb1\x28\xdc\x7a\xdd\x55\x3c\x78\xc7\x5c\x12\xe2\ -\xb5\xed\x82\x3b\x5d\x6e\x5e\x7e\x6b\x39\x2f\xbf\xb5\x42\xf7\xe3\ -\xbb\x70\xe2\x44\xc3\x31\x5e\x7a\xfa\x0f\xdc\x78\xfb\x7d\x21\x5d\ -\x12\xb4\xb9\xc8\x7e\xe9\x0b\x99\x7c\xf7\x64\xd1\x76\xf6\xe7\x17\ -\x08\x80\x33\xd5\x33\x0d\x14\xcd\x5b\x97\x3b\xf7\x1b\x1f\xfc\x93\ -\x96\x92\xc4\xa4\x71\xc6\xac\xb3\xfa\x3a\xc5\xa3\x86\xf0\xf0\x83\ -\x0b\x02\x3a\x79\x59\xb3\x69\x07\x7f\x7d\xfe\xad\xb0\x39\x42\x0e\ -\x36\x4e\x87\x8d\xa5\xaf\xfc\x93\x1b\x6e\xbb\x8f\x61\x23\xc7\x85\ -\xa4\x4f\x8f\x17\xe1\xb6\xa8\xdf\x00\x7e\x7f\xf6\xe7\x17\x2e\x01\ -\x84\x72\x9d\xc6\xd9\x3f\x6e\x8f\x97\x9a\x3a\xe3\xb3\xfe\xce\x9a\ -\x32\x0e\x8b\xc5\xac\xd8\x13\x4a\x32\x52\x93\xf9\xe6\x3d\x81\x4d\ -\xf7\xab\xeb\x4e\xf0\x97\x67\xdf\x0c\xab\xe3\xe3\x50\xa1\x7a\xbd\ -\xbc\xbb\xf8\x79\xae\x5f\xf0\x15\x2e\x1b\x5b\x1a\x92\x3e\x6d\x1e\ -\xf1\x25\xba\x15\x00\xc9\xb5\x5a\x1b\xae\xad\x3b\x81\xd7\x1b\xa4\ -\x1c\x47\x1a\x98\x31\x39\x34\x6a\x6a\xe2\xab\x80\x7c\xc7\x0d\xd3\ -\xb9\xff\xb6\x6b\x88\x8f\xd3\xb6\xa9\x65\x77\xb8\x78\x71\xe9\x27\ -\xbc\xf6\xce\x2a\x5c\x1e\x4f\x90\x2c\x0c\x7f\xa4\x54\xf9\x60\xe9\ -\xcb\x58\xac\x51\x21\x99\x09\x74\xb8\xc4\x05\x49\x42\xce\x11\x80\ -\xed\xf5\x32\xdb\xed\x51\x47\x6a\x6d\xf8\x68\xad\xf1\x6f\xff\x84\ -\xf8\x38\xc6\x8c\x18\x68\xb4\x19\x7d\x82\xe2\x51\x83\xf9\xe1\xd7\ -\x6e\x63\x60\x61\x8e\xe6\x7b\xd7\x6d\xde\xc9\x13\x0b\x97\x52\x77\ -\xbc\x29\x08\x96\x45\x1e\x3e\x11\x58\x44\x72\x4a\x1a\x39\xf9\xc1\ -\x75\x25\xb6\x39\x89\x7d\x63\x9b\x1c\x71\xdb\x58\x71\xda\x61\xe7\ -\x1c\x01\x70\x7b\xbd\x53\xb5\x3a\xff\x00\x54\xd5\x1a\x5f\xea\x7b\ -\xd2\xb8\x61\x58\x2d\x66\x95\xde\x60\x92\x99\x9e\xc2\x37\xbe\x72\ -\x03\xd7\x4e\xd7\x3e\x65\xad\xac\x6d\xe0\xcf\xcf\xbd\xc9\x86\x8a\ -\xf0\x70\x16\x0b\x27\x3c\x6e\x37\x6f\xfd\xfb\x19\xee\x7e\xe8\x11\ -\x12\x93\x53\x82\xd6\x8f\x04\xda\x9c\xea\xfd\xc0\x8f\x4e\x7d\x76\ -\x8e\x00\x08\xc4\x95\x81\xb8\x5a\x1c\xad\x69\xe8\x99\x65\x3a\x50\ -\x32\xc6\x4c\xed\x1d\x2c\xa2\xad\x56\xbe\x74\xd3\x0c\xee\x5d\x30\ -\x87\xb8\x58\x6d\xb1\xfa\x36\xbb\x93\xe7\x5e\xff\x88\xd7\xdf\xff\ -\x0c\x8f\x37\x7c\xdc\x76\xc3\x8d\x8e\xf6\x56\xde\x5d\xfc\x1c\x5f\ -\x7e\xf0\x7b\x08\x11\xbc\x7d\x2c\xbb\x5b\xcc\xe1\x52\x02\x20\x91\ -\x57\x06\x32\x03\xa8\x6b\x30\x7e\x3a\x37\x76\xc4\x00\xa3\x4d\xe8\ -\x95\x94\x8c\x1e\xca\x0f\x1f\x5c\xc0\x80\x00\xa7\xfb\x7f\x7c\x66\ -\x09\xf5\x27\x9a\x83\x60\x59\xef\xa3\xa6\xea\x30\x1b\xd7\x2e\x67\ -\xd2\x55\xc1\x73\x64\xb3\xb9\x18\x7e\xf6\xbf\x4f\x0b\xc0\xfa\x2a\ -\x19\x87\x54\xc7\x07\xd2\x68\x63\x6b\x47\x4f\xed\xea\x11\x09\xf1\ -\x71\x0c\x28\x30\x03\x7f\xf4\x24\x37\x2b\x9d\xef\xde\x3f\x9f\x69\ -\x97\x8f\xd6\x7c\xef\xc1\xa3\xb5\x3c\xb1\x70\x29\x5b\x76\x45\xbe\ -\x17\x5f\xa8\x59\xbf\x72\x19\x83\x86\x8e\x22\x33\x27\x38\x59\xab\ -\x3b\x5c\x22\xe1\x1f\xdb\x64\xda\xb7\xc6\x8a\x66\x38\x4b\x00\x62\ -\x2d\x4c\x44\xa2\x69\x7e\xd7\xe9\x82\x6d\x75\x82\x86\x66\x63\x4b\ -\x7f\x0d\x1d\x90\x67\x96\xf5\xd2\x89\xe8\xa8\x28\xbe\x72\xcb\x2c\ -\xee\x9e\x3f\x9b\x98\x68\x4d\x89\xa0\xe9\xb0\xd9\x59\xf8\xea\x32\ -\x96\x7e\xb4\x36\x2c\x4e\x85\x22\x11\xaf\xd7\xc3\x87\x6f\xbe\xc4\ -\xdd\xdf\xf8\x4f\x14\x45\xff\xa5\x80\x04\x12\x5d\x7c\x09\x78\x0a\ -\xce\x12\x00\x21\xd5\xcb\xb5\xac\xff\xdb\x9d\xb0\xe6\xb0\x82\xc3\ -\xe5\xc5\x61\xb3\xe9\x6c\xa6\x36\xcc\xb0\x5f\x7d\x98\x70\x32\x77\ -\x62\x5e\x76\x86\xa6\xfb\xa4\x94\x7c\xb8\x6a\x13\x4f\xbe\xf4\x1e\ -\xcd\xad\xe1\x5b\x07\x32\x52\x68\xa8\xab\x61\x7b\xd9\xe7\x8c\x9b\ -\xa8\x29\x1d\x87\xdf\xb8\x24\x73\x39\x5f\x00\x90\x8c\xf3\x77\xf9\ -\x2f\x25\x94\x55\x2b\xb8\xbc\x60\xb3\x75\x18\x1e\xa5\x65\x0a\x80\ -\x3e\xcc\x0d\x60\x77\x7f\xdf\xa1\x6a\xfe\xf8\xcc\x12\x76\x84\x89\ -\x2b\x78\x6f\x61\xdd\xaa\x0f\x19\x39\xae\x94\xa8\x20\x04\x0e\xd9\ -\x5d\x8c\x3d\xf5\xff\xa7\x05\x40\x8a\x33\x1f\x76\x47\x75\xab\xa0\ -\xc5\xe1\xfb\x7f\x5b\x87\xf1\x8a\x6f\x06\xff\x84\x9e\xb6\x0e\x1b\ -\xff\x7c\xe5\x7d\xde\xf9\xf4\x0b\x54\xd5\x9c\xee\xeb\x8d\xad\xa3\ -\x9d\x0d\x6b\x96\x33\x75\xf6\xf5\xba\xb7\xdd\xe9\x92\xa7\x37\x18\ -\x14\x80\xcd\x9b\x65\x14\xe0\x97\x13\xbd\x94\xb0\xef\xc4\x99\xa9\ -\x82\xcb\x6d\x7c\x08\x70\x76\x66\xaa\xd1\x26\xf4\x19\x54\x55\xf2\ -\xce\x27\x9f\xf3\xe5\xef\xfe\x9a\xb7\x3e\x5e\x6f\x0e\xfe\x20\x52\ -\xf6\xf9\x4a\x6c\x9d\xfa\xbf\x60\x1d\x6e\x11\xfb\xf4\x66\x19\x0f\ -\xa7\x66\x00\xd9\x8c\x00\xff\x36\x00\xab\x5a\xcf\xcd\x7b\xee\x75\ -\x1b\xeb\xca\x29\x84\x20\xbb\x5f\x9a\xa1\x36\xf4\x15\x76\xed\x3f\ -\xca\x1f\x9f\x59\xca\x9e\x83\xc6\x07\x7e\xf5\x05\xdc\x6e\x17\x5b\ -\x36\xad\x65\xca\x8c\x6e\x2b\x7c\x69\x42\x05\xa2\xe1\x1a\xe0\x2d\ -\x05\xc0\x62\xf1\xfa\x35\xfd\x6f\x77\x72\x41\xea\x63\x8f\xc7\xd8\ -\xd0\xcd\xc4\xf8\x38\xcd\xbb\xd5\x26\xda\x68\xeb\xb0\xf1\xe7\x67\ -\xdf\xe4\x3f\xfe\xe7\xcf\xe6\xe0\x0f\x31\x15\x1b\xd6\xe2\x71\xeb\ -\x3f\xc6\x54\xd5\x17\xf2\x6f\x05\x90\xaa\x18\xd5\xd5\x06\xa0\xc3\ -\x03\xc7\xda\x04\x7b\x8e\x0b\x5c\xe7\x39\x73\x79\xbc\xc6\xce\x00\ -\xba\x2b\xd6\x60\x72\x06\x21\x04\x03\x0a\xb2\xfd\xbe\x5e\x55\x55\ -\xde\xfc\x68\x3d\xcf\xbc\xfa\x21\xed\x9d\xc6\x9e\xf4\xf4\x55\xec\ -\xb6\x0e\x76\x6f\x2f\x63\x4c\x89\xa6\x0c\x7d\xdd\xe2\x51\x45\x09\ -\x9c\x5a\x02\x08\x06\x81\x6f\x7d\xdf\xe6\x80\x86\x4e\x41\x9b\x13\ -\x3a\x5d\x82\x0e\x27\x17\x0c\xfa\xb3\x51\x0d\x8e\xe6\x4a\x4e\x32\ -\x05\xc0\x1f\x2e\x1b\x52\xc4\xc3\x0f\x2e\xf0\xbb\xf8\xc6\xb6\xdd\ -\x87\xf8\xe3\x33\x4b\x39\x70\xb4\x26\xc8\x96\x99\x74\x47\xd9\xe7\ -\xab\x74\x17\x00\x97\x57\x14\xc2\x49\x01\x58\x77\x44\x5c\xa3\x4a\ -\x41\x9b\x03\xdc\x1a\xf7\x74\x82\x31\x3d\xd1\x82\xd6\x50\xd4\xbe\ -\x46\x6a\x72\x22\xdf\xb8\x6b\x1e\xf3\x66\x4d\xf2\xcb\x59\xaa\xb1\ -\xa5\x8d\x7f\x2c\x7a\x97\x8f\x3f\x2b\x33\xfc\x78\xd7\xc4\xc7\x89\ -\x86\x63\x34\x1c\xab\x26\x2b\xb7\x40\xb7\x36\x9d\x1e\x99\x01\x27\ -\x05\xa0\xd9\x2e\x52\x3d\x11\xba\x99\x1b\x6d\xed\xb6\xc0\x71\x9f\ -\xc4\x62\x51\x98\x37\x6b\x12\xdf\xb8\x73\x1e\x29\xc9\xdd\x17\xdf\ -\xf0\x7a\x55\x96\x2c\x5b\xcb\xc2\xd7\x96\xd1\x69\xb3\x87\xc0\x42\ -\x13\x2d\xec\xde\x5e\xae\xab\x00\x38\x3c\xc2\x77\x0a\xf0\xd7\x0d\ -\x72\xe0\xf1\xd6\x00\x22\x80\x4e\x62\xb1\x18\x3b\x00\x2d\x56\x33\ -\x03\xd0\xf9\x14\x8f\x1a\x7c\x32\x3d\x97\x7f\xfe\xe4\xe5\x3b\x0f\ -\xf0\xc4\x33\x4b\x38\x5c\x55\x17\x64\xcb\x42\x4b\x5a\x4a\x12\x69\ -\x29\x89\xa4\x24\x25\x90\x9c\x14\x8f\xd7\xeb\xc5\xee\x70\xd1\x61\ -\x73\x50\x73\xec\x04\x1d\x11\x24\x74\x7b\x77\x94\x33\x7d\xce\x8d\ -\xa0\x53\xa5\x2b\x97\x17\xe5\x2f\x9f\xc9\x4c\x6b\x9c\x85\x1e\x15\ -\xcf\x0b\x86\xbf\xb2\x16\xcc\x1c\x00\x67\xc8\xca\x48\xe5\xdb\xf7\ -\xde\xcc\xec\x2b\xfd\x8b\xe9\x6a\x68\x6c\xe1\xef\x2f\xbe\xcd\xf2\ -\x75\x5b\x82\x6c\x59\xf0\x89\xb6\x5a\x19\x37\x6a\x10\x25\xa3\x86\ -\x32\x7c\x50\x21\x43\x06\xe4\x91\x9e\x9a\x74\xc9\xeb\xa5\x94\x54\ -\x1d\x3b\xce\x9e\x03\x55\x6c\xd8\xba\x87\x35\x9b\x76\x60\xb3\x39\ -\x42\x68\xb1\x36\xda\x5a\x9b\xa9\xae\x3c\x4c\x41\xff\x41\xba\xb5\ -\x99\x94\x24\x63\xa2\xc1\x00\x00\x20\x00\x49\x44\x41\x54\xc0\x64\ -\xab\xe2\x51\x2f\xd3\x58\x02\xe0\x1c\x84\xc1\x03\xd0\x74\x44\xf1\ -\x3d\xfc\x5f\xbe\x79\x06\xf7\xcc\xf7\x2f\x5e\xdf\xed\xf1\xb2\xf8\ -\xbd\x55\x3c\xff\xc6\xc7\x86\xd4\xd3\xd3\x8b\xb8\xd8\x68\xa6\x4f\ -\x1a\xcb\xac\x2b\x8b\x29\x19\x35\x84\xd8\x18\xff\x63\xd9\x84\x10\ -\x14\xe5\x65\x51\x94\x97\xc5\x35\xd3\x26\x60\x73\x38\x79\x7f\xc5\ -\x06\x16\xbd\xb9\x9c\xa6\xe6\xb6\xee\x1b\x30\x80\xfd\xbb\xb6\xe8\ -\x2a\x00\x0a\x8c\xb1\xaa\x82\x1e\xd5\x64\x56\x0c\x16\x00\x4f\xa4\ -\x6e\x5e\xe8\xc4\x94\x09\x23\xf9\xde\x57\xe7\x53\x90\xe3\x9f\x3b\ -\xf4\xc6\x2d\x7b\xf9\xd3\xb3\x4b\xa9\xac\x35\x3e\x89\x4b\xa0\x0c\ -\x1f\x5c\xc8\xad\xd7\x4e\x65\xc6\xe4\x71\xba\x6d\x02\xc7\xc7\xc6\ -\x70\xfb\xf5\xd3\x98\x37\x73\x12\xff\xfa\xf7\x07\xbc\xf1\xe1\x9a\ -\xb0\xdb\x04\x3d\x72\x68\xaf\xae\xed\xb9\x25\x03\xad\x28\x4a\x7a\ -\x4f\x1a\xb1\x18\xbc\x04\x70\x1b\xec\x87\x60\x14\x85\xb9\x99\x7c\ -\xf7\xab\xb7\x30\xa5\xc4\xbf\x14\x8e\xb5\xf5\x8d\xfc\xed\x85\xb7\ -\xf8\x6c\x63\xe4\x16\xdc\x98\x5c\x72\x19\x77\xdd\x3c\x8b\x92\x51\ -\x43\x82\xd6\x47\x7c\x5c\x0c\xdf\x7f\x60\x3e\xa5\x63\x87\xf1\x8b\ -\x3f\x2d\x0a\x8b\x6a\x57\xa7\x68\x6c\xa8\xa3\xa3\xbd\x95\xc4\x24\ -\x7d\xd2\x86\xa9\x1e\x99\x67\x2d\xce\x53\x77\x0f\x4a\x63\xde\xd6\ -\x63\x0a\xcd\x01\xec\x89\x18\x3d\x03\xb0\xdb\x23\x77\x0a\x1b\x08\ -\x71\xb1\xd1\xdc\x77\xeb\x35\xdc\x71\xe3\x74\xbf\x4e\x40\x7c\x05\ -\x37\x56\xf0\xf2\x5b\xcb\x23\xb6\xe0\xc6\xd8\x11\x83\xf8\xe6\x3d\ -\x37\x30\x66\x78\xe8\x92\xbe\x5e\x59\x3a\x8a\xbf\xfe\xe2\xdb\x7c\ -\xff\x97\x4f\xd1\x11\x46\x4e\x50\x95\x07\xf7\x31\x72\xfc\x44\x5d\ -\xda\xf2\x48\x91\x69\x55\x20\x23\x35\x0e\xae\x1a\xa8\x52\x56\x2d\ -\xa8\x69\xd3\xb6\xcb\x68\xb5\x6a\xcb\x11\xa7\x37\x6d\x9d\x91\xb3\ -\x93\xdb\x13\x84\x10\xcc\x9e\x32\x9e\x6f\xdd\x7b\x13\x59\x19\xfe\ -\x05\x3f\x7d\xb6\x71\x07\x7f\x7b\x21\x72\x0b\x6e\xe4\x66\xa5\xf3\ -\x9d\x7b\x6f\x66\xfa\x64\xbf\x03\x55\x75\x65\xf8\xe0\x42\x1e\x7a\ -\xe8\xeb\xfc\xe5\x6f\xff\x30\xdc\xdf\xe5\x14\x47\x0e\xed\xd5\x4d\ -\x00\xbc\x2a\x69\x56\x89\xc8\x00\x89\x22\x60\x42\x81\xc4\x79\x14\ -\x4e\x74\xfa\x2f\x02\xf1\xf1\xdd\x9f\x31\x07\x93\xf6\x8e\xf0\x51\ -\xe7\x60\x31\xb8\x7f\x1e\x3f\x78\x60\x3e\xc5\x7e\x4e\x7d\x23\x3d\ -\x03\x6f\x4c\xb4\x2f\x2b\xd1\x57\x6e\xd1\x9e\x95\x48\x4f\xaa\x5b\ -\x05\x4a\xea\x20\x66\x5f\x7f\x1b\x1f\xbd\xfd\x6f\xc3\xec\x38\x9b\ -\xaa\xc3\xfb\x75\x6b\x4b\x55\x65\xa2\x15\xd4\x7e\xa7\x12\x81\x2a\ -\x02\x26\x15\x49\x3e\x3b\x74\x6e\xc4\x5f\x57\xc4\xc6\x19\xeb\x8a\ -\xdb\xda\xde\x89\xd7\xab\xf6\xca\x8a\x40\x49\x09\xf1\x7c\xed\xcb\ -\xd7\x71\xcb\x35\x53\xfc\xfa\xfd\x6c\x0e\x27\x2f\x2e\xf9\x84\xd7\ -\xde\x5d\x85\xdb\x13\x99\x19\x78\xaf\x9a\x38\x9a\xef\xde\x7f\x8b\ -\xe6\xac\x44\xc1\x60\xdf\x71\xdf\xb8\x18\x53\x32\x99\xc3\xfb\x77\ -\xb1\x6f\xd7\x56\x83\x2d\x82\xf6\xb6\x16\x6c\x1d\xed\xc4\x27\x5e\ -\xfa\x88\xd3\x5f\x3c\x52\xc4\x59\x41\x9c\x33\x9f\x8c\x52\x60\x72\ -\x7f\x95\xcf\x0e\x29\x38\xfd\xd8\x5f\x8b\x8d\x8b\xf7\x39\x27\x18\ -\xb4\x63\xaa\xaa\x2a\xc7\x9b\x5a\xc8\xc9\xec\xd1\x5e\x66\x58\xa1\ -\x28\x82\x1b\x66\x4d\xe2\xa1\xbb\xe6\x91\x9a\xec\x5f\x99\xc6\x4f\ -\xd7\x55\xf0\x8f\x17\xdf\xa1\xa1\xb1\x25\xc8\xd6\x05\x87\xa2\xbc\ -\x2c\xbe\xff\xd5\xf9\x4c\x2a\x0e\x9f\xda\x8e\x67\x3f\xd1\xb3\xae\ -\xbb\x95\xc3\x07\xf6\xe0\x76\x19\xbf\x29\x58\x7f\xac\x9a\x81\x43\ -\x2f\xeb\x71\x3b\x5e\x29\x63\xac\x42\x12\x2b\xcf\x9b\xf1\x27\x44\ -\xc1\xd4\x01\x2a\x1b\x2a\x15\x3a\xba\xd9\x63\x53\x2c\x16\xa2\xa2\ -\xa2\x0d\xfd\x62\xea\x8e\x37\xf7\x1a\x01\x18\x35\x74\x00\x0f\x7f\ -\x6d\x3e\x23\x06\xfb\x57\x25\xe6\x50\xe5\x31\x9e\x58\xb8\x84\x8a\ -\x9d\x91\x99\x81\x37\x3e\x36\x86\x7b\x6f\x9d\xc3\x97\x6e\x9c\x41\ -\x94\x35\xbc\x9c\xba\x06\xa4\x49\xb6\x9f\x0c\x7f\x4f\x4c\x4e\xa1\ -\xf4\x8a\x19\x7c\xbe\xfa\x23\x83\xad\x82\x06\xbd\x04\x40\x15\x16\ -\xab\x14\x17\x4f\x04\x92\x14\x03\xb3\x86\xa8\x54\xb6\x08\x1a\x3a\ -\xa0\xd5\x21\xb0\xb9\x2f\xfe\xa2\x8f\x8f\x4f\xa0\xd5\x40\x01\xa8\ -\xaa\x3d\xce\xf8\x91\x83\x0d\xeb\x5f\x0f\xd2\xd3\x92\xf9\xe6\xc9\ -\xaa\x3b\xfe\x14\xd9\xec\xb4\xd9\x59\xf8\xda\x32\x96\x2c\x8b\xdc\ -\x0c\xbc\x57\x5f\x59\xac\x69\x53\x33\xd4\xf4\x4f\x93\xec\xaa\x17\ -\x78\x4f\x3e\xf3\xa5\x53\x66\x50\xf6\xc5\x6a\x5c\x4e\x63\x3d\x06\ -\x1b\x8e\x55\xeb\xd2\x8e\xaa\xa2\x58\xe9\x22\x13\x90\x22\x7c\x2a\ -\x38\x20\x0d\x40\xe2\xf0\x40\x4d\xab\xa0\xaa\x55\xd0\xea\x38\x23\ -\x06\xb1\x71\xf1\xb4\xb6\x18\x57\x1c\xe4\x60\x65\xad\x61\x7d\xf7\ -\x14\xab\xc5\xc2\x6d\xd7\x5f\xc5\x03\xb7\xcf\x25\x3e\x3e\xb6\xdb\ -\xeb\x4f\x65\xe0\x7d\xea\xe5\xf7\x68\x6a\x31\x3e\x1f\x63\x20\x0c\ -\x2a\xca\xe5\xe1\x07\x17\xf8\xbd\xa9\x69\x14\x56\x05\x62\xa3\x7c\ -\xe9\xef\x01\x62\x62\xe3\x19\x39\xb6\x94\x2d\x9b\xd6\x1a\x6a\x57\ -\x7d\x9d\x4e\x02\x00\xc2\x8a\x20\xc6\xdf\x72\xe0\xb1\x56\x18\x9c\ -\x21\x19\x9c\x21\x71\xab\xd0\x66\x87\x76\xa7\xe0\x93\x7e\x69\xd4\ -\xeb\xa4\x4a\x81\xb0\xff\x70\x64\x0a\x40\xe9\xd8\x61\xfc\xe0\xab\ -\xf3\xfd\xae\xba\x13\xe9\x19\x78\x13\xe2\xe3\x78\xf0\x4b\xd7\x72\ -\xeb\xb5\x53\x23\x62\xd3\x56\x05\x6c\xe7\x9d\xfe\x8d\x2b\xbd\xd2\ -\x70\x01\x68\x69\x6e\xc4\xe3\x76\x63\x8d\xea\xf1\x09\x89\xb0\x6a\ -\x2d\x06\x72\x8a\x28\x05\x32\x12\x20\x23\x41\x32\xbc\x28\x8d\xad\ -\xdb\x7b\x6a\x4b\xe0\xec\x39\x58\x89\xcb\xe3\x89\x98\xd0\x60\xad\ -\x45\x36\xdb\x3b\x6d\x3c\xfb\xda\x47\x2c\x59\xb6\x36\x22\x63\x1f\ -\x84\x10\x5c\x33\x6d\x02\xdf\xbe\xe7\xa6\x2e\x03\x74\xc2\x0d\xbb\ -\xeb\xc2\x25\x6f\x66\x4e\x1e\xe9\x99\xd9\x34\x1d\x37\xb0\x20\xae\ -\x94\xb4\xb5\x36\x91\xde\xcf\xff\xec\x4e\x17\xc3\xab\x22\xba\x5c\ -\x02\xf8\x4b\xae\xc1\x47\x36\x4e\x97\x9b\x3d\xfb\x2b\x19\x7b\x99\ -\x7e\x81\x12\xc1\x20\x26\x3a\x8a\xbb\x6e\x9e\xc5\x57\x6e\x99\xe5\ -\x57\xe0\x8a\xaa\xaa\xbc\xf3\xe9\x17\xfc\xf3\xdf\x1f\xd0\xd6\xde\ -\x19\x02\x0b\xf5\x67\xf8\xe0\x42\x1e\x7e\x70\x01\xa3\x87\x0d\xf0\ -\xeb\xfa\xda\xfa\x46\x52\x92\xe2\x49\x88\x8f\x0b\xae\x61\x7e\x10\ -\x6d\x05\x8b\xe0\xf4\x1e\xc0\x29\x86\x5e\x36\x96\x0d\xc7\x3f\x31\ -\xc6\xa8\x93\xb4\x34\x37\xf6\x58\x00\x24\xe7\x15\x07\x0d\x94\xbc\ -\x2c\xe3\xcf\x6c\x37\x6e\xdd\x1b\xb0\x00\x34\xda\xa0\xaa\x45\x70\ -\x59\x96\x24\x26\x48\x93\x88\x94\xa4\x78\x16\x3d\xf1\xa8\xdf\xe7\ -\xdb\x3b\xf6\x1d\xe1\x8f\xcf\x2c\x61\xdf\x21\xe3\x96\x56\x3d\x21\ -\x39\x29\x81\x6f\xdc\x79\x3d\x37\x5e\x7d\x85\x5f\x99\x88\x1c\x4e\ -\x17\x2f\xbf\xb5\x82\x57\xde\x5e\xc1\xa2\x27\x1e\x0d\x0b\x01\x88\ -\x52\x60\x58\xa6\x64\x77\xc3\xb9\xf6\x0f\x18\x72\x19\x1b\x3e\x33\ -\x56\x00\x5a\x9b\xf5\xf1\xee\xb4\x02\x2e\xa0\x47\xdf\x76\x6e\x96\ -\xf1\x69\xb9\x57\x6f\xd8\xc6\xd7\xbe\xac\x3d\x7d\x72\x65\xab\x60\ -\x6b\x8d\x6f\xa7\x37\x3d\x1e\x8a\x52\xcf\xc8\xbd\xdd\x0d\x31\x56\ -\xdf\x66\x68\x4f\x49\x88\x8f\xf3\xeb\xa1\x6e\x6b\xef\xe4\xb9\xd7\ -\x3f\x66\xc9\xb2\x35\xa8\x6a\x78\x45\xa3\xf9\x83\xa2\x08\xe6\x5c\ -\x35\x81\xef\xde\x77\xb3\xdf\x3e\x0c\xeb\x36\xef\xe4\xcf\xcf\xbd\ -\x19\x96\x2e\xcb\xc3\x32\x25\x4e\x0f\x1c\x6a\x3a\xf3\x10\xe4\x15\ -\xf4\xc7\x6a\x8d\x32\x34\x23\xb6\x1e\x9b\xee\x02\xb0\x22\x70\x21\ -\x7b\x26\x00\x39\x61\x30\x03\x38\x5c\x55\xc7\xa1\xca\x63\x7e\x97\ -\x09\xb3\xb9\x60\x7b\xbd\xe0\xd8\xc9\xd8\x87\xe4\x58\x28\x48\xf1\ -\x0d\x38\x09\xec\xaa\x17\x1c\x38\x21\x88\xb2\xc0\xd0\x7e\xbe\x8d\ -\xcf\x60\xd6\x1f\xf5\x7a\x55\x96\x7c\xb8\x86\x85\x8b\x3f\x8a\xd8\ -\x94\x5c\x63\x86\x0f\xe4\xe1\x07\x17\x30\x6c\x90\x7f\xa9\xab\x8e\ -\x54\xd5\xf1\xa7\xe7\xde\x64\xf3\xb6\x7d\x41\xb6\x2c\x70\x04\x30\ -\x26\x57\x62\x55\xce\x14\xc4\xb1\x58\xac\xe4\xe4\x17\x52\x7d\xf4\ -\x90\x61\x76\xb5\xe9\x30\x03\xb0\x28\x48\x2b\x92\x1e\x1f\xe0\xc7\ -\xc7\xc6\x90\x93\x99\x4e\xdd\x71\xe3\x8e\x02\x01\xde\xfe\x64\x3d\ -\x0f\x3f\x78\xeb\x45\x7f\xe6\x51\xa1\xd3\x09\x2d\x0e\x41\x7d\x07\ -\xd4\xb5\x09\xce\xde\x4e\x2b\x4c\xf5\x0d\x70\x55\x42\x45\xad\xa0\ -\xaa\xc5\xf7\xc7\x76\x79\x61\x67\xbd\xe0\x68\xb3\x60\x42\x81\x4a\ -\x5a\x10\x66\xa6\xe5\x3b\x0f\xf0\xa7\x85\x4b\x39\x54\x79\x4c\xff\ -\xc6\x43\x40\x7a\x5a\x32\xdf\xba\xfb\x46\xe6\x4e\x9b\xe0\x97\x0f\ -\x83\xcd\xe6\xe0\xd9\xd7\x3f\xe2\x8d\x0f\xd6\xe0\xf1\x86\xbf\xcb\ -\xb2\x00\x46\x66\x4b\x12\xa2\x61\x6b\xad\xef\xb9\xe9\x97\x95\x67\ -\xac\x00\xb4\x36\xeb\xd1\x8c\x3c\xb5\x04\xe8\x31\xc3\x06\xe6\x19\ -\x2e\x00\xcb\x56\x97\xf1\xf5\x3b\xaf\x27\xf1\xe4\x54\x5b\x95\x70\ -\xe0\x84\xa0\xb2\x55\xd0\xe9\xa4\xcb\xd3\xce\x7d\x0d\x82\x13\x9d\ -\xd0\xe6\x10\xd8\x2f\x32\xb3\xeb\x70\xf9\xaa\x21\x4f\x2a\x92\x64\ -\x27\xea\x33\x35\x8f\xf4\x94\x5c\x56\x8b\x85\x5b\xaf\x9b\xca\x03\ -\x77\xcc\xf5\x6b\x79\x23\xa5\x64\xd9\xea\xcd\x3c\xf9\xf2\x7b\x61\ -\x9b\x75\xa7\x2b\xfa\xa7\x49\xe2\xa2\x60\x63\xb5\x20\x33\xdb\xbf\ -\x7c\x8b\xc1\xc2\x61\xef\x79\x10\x9c\x02\xd2\x2a\x24\xae\xf3\x5d\ -\x81\x03\x61\xd8\xa0\x42\xc3\x93\x4d\x74\xda\xec\x2c\x7e\x6f\x35\ -\x0f\xdc\x71\x2d\x52\xc2\xfa\xa3\x0a\x27\xfc\xdc\x3c\x77\xab\x50\ -\xdf\xde\xf5\x17\xa1\x4a\xf8\xa2\x52\x30\x24\x1d\x32\x13\x7d\xbb\ -\xc3\x36\x97\x4f\x58\xfa\xc5\x4b\x52\x2f\x32\x06\xaa\x5a\x05\x95\ -\xcd\xe0\xf0\x08\x14\x01\x59\x89\x92\xc1\xe9\x1e\xde\xf9\x78\x1d\ -\xff\x7a\xf5\x03\xec\x76\xe3\x7d\xcb\x03\xa1\x78\xd4\x10\x1e\x7e\ -\x70\xbe\xdf\x89\x47\xf7\x1d\xaa\xe6\x4f\xcf\xbe\xc9\xb6\x3d\xc6\ -\xbd\x35\xf5\x20\x2b\x51\x72\xd5\x00\x49\xed\x91\x2c\x43\xed\xb0\ -\xdb\x7a\x7e\x2a\xa4\x28\xa8\x56\x29\xd0\xc5\xaf\x71\xd8\x40\xfd\ -\x52\x16\xf7\x84\x57\xdf\x5b\xcd\x4d\x73\xae\xa0\x43\xa6\xfa\x3d\ -\xf8\xb5\x20\x25\xec\x6f\x14\xec\xbf\x60\x09\x26\xc8\x4a\x94\x8c\ -\xc9\x91\x24\xc5\x80\xc7\x0b\xdb\xea\x04\x95\x2d\xe7\x8a\xca\x96\ -\xed\x7b\x58\xfd\xd1\x52\x4e\x1c\x8f\xcc\x94\x5c\x59\x19\xa9\x7c\ -\xe7\xbe\x9b\x99\x35\xc5\xbf\xc4\xa3\x2d\x6d\x1d\xfc\xf3\x95\xf7\ -\x79\x6f\xc5\x86\x88\xdc\xd4\xbc\x18\x29\xb1\x70\xf5\x98\x54\x5e\ -\x34\xd0\x06\xa7\xd3\x81\x94\x2a\x42\x04\xee\x50\x65\x15\xd2\x6b\ -\x05\xd9\x4c\xe0\x59\xc1\x4f\x33\x6c\x50\x7e\x8f\xdb\xd0\x03\x9b\ -\xcd\xc1\x9f\x9f\x7d\x93\x69\x37\x3d\xe0\xf7\x3d\x85\x29\x92\x31\ -\xb9\x92\xf5\x47\x94\xd3\x65\xcf\x03\xa1\xa1\x43\xb0\xe2\xa0\x20\ -\x21\xca\xe7\x41\x76\xf6\xf3\xde\xda\xdc\xc8\xca\x65\x6f\x72\x60\ -\x8f\x81\x1e\x53\x3d\x20\xda\x6a\xe5\x4b\x37\xcd\xe0\xde\x05\xfe\ -\x25\x1e\xf5\x7a\x55\xde\xfa\xb8\xf7\x96\x15\x2b\xca\x49\x45\x51\ -\x14\xe3\x1c\xb3\xa4\xc4\x61\xb3\x13\x97\x10\x78\x3e\x0e\x8b\x82\ -\xd3\x0a\xca\x89\xae\x57\xc7\xfe\xd1\x2f\x2d\x85\x8c\xd4\x64\x1a\ -\x5b\x8c\x5f\xdb\xad\xfc\x7c\x2b\x31\xd9\x5f\x30\xba\xd8\xbf\x72\ -\x4a\x49\xb1\x10\x6d\x81\xd4\x38\x49\x8b\xa3\x67\x62\x28\x25\xe7\ -\x44\x50\x7a\xdc\x6e\x36\xae\xfb\x94\x8d\x6b\x97\x87\x4d\x56\x19\ -\xad\x5c\x51\x7c\x19\xdf\x7b\x60\x3e\x85\xb9\x99\x7e\x5d\x5f\xb1\ -\xf3\x00\x7f\x7a\xf6\x4d\x0e\x1e\x8d\x4c\x17\x6d\x7f\xb0\x5a\x2c\ -\x24\x25\xc6\xd1\xda\x66\x9c\x83\x96\xc3\xd1\xd9\x23\x01\x50\x84\ -\xb0\x59\x41\xea\x76\xf8\x5a\x32\x66\x08\x9f\xac\x29\xd7\xab\xb9\ -\x1e\xf1\xe9\xfb\x6f\x90\xd6\x2f\x9b\xfc\xc2\xee\xf3\xc8\x1d\x6c\ -\x14\x08\xe0\x58\x37\x7b\x00\x5a\xd9\xb7\x6b\x2b\xab\x3f\x7a\xcb\ -\xd0\x40\xa9\x9e\x90\x97\x9d\xc1\xf7\xee\x9f\xcf\xd4\x89\xa3\xfc\ -\xba\xbe\xa1\xb1\x85\x7f\xbc\xf8\x0e\xcb\xd7\x6f\x09\xbb\x8c\xba\ -\xc1\x20\x25\x31\xc1\x50\x01\xb0\xdb\x6c\xa4\xf5\xe0\x04\x5e\x51\ -\xe8\xb0\x0a\x38\xa1\xd7\x9f\x6a\xe2\xd8\xe1\x61\x23\x00\x1e\xb7\ -\x9b\xa5\x2f\xfd\x93\xdb\xef\xfb\x26\x39\x79\x5d\xc7\xd6\x3b\x3d\ -\xbe\xa3\x3e\xbd\xa8\xae\x3c\xc8\x67\x1f\xbf\x43\x6d\xd5\x11\xdd\ -\xda\x0c\x25\xb1\x31\xd1\xdc\x3d\x7f\x36\x77\xdd\x3c\x93\x68\x3f\ -\x02\x4e\x5c\x1e\x0f\x8b\xdf\x5d\xcd\x0b\x4b\x22\xbb\xce\x80\x56\ -\x92\x12\x8c\xcd\x86\xd5\xd3\x1c\x1c\x56\x85\x26\xab\x0a\x8d\x7a\ -\x3d\xfa\xa5\x63\x86\xe9\xd4\x92\x3e\x38\x1d\x36\x16\x3f\xf7\x37\ -\xae\xbf\xf5\x5e\x86\x8c\x18\x1d\xf4\xfe\x8e\x1e\xdc\xcb\xc6\x75\ -\xcb\x39\x7a\x50\xdf\xfc\xed\xa1\x64\xfa\xe4\xb1\x7c\xf7\xbe\x9b\ -\xfd\x4e\xb0\xb2\xbe\x7c\x17\x7f\x7d\xee\x2d\xaa\x8e\x1d\x0f\xb2\ -\x65\xe1\x47\x5c\x9c\xb1\x09\x71\x55\xb5\x67\x3e\x14\x56\x45\x1e\ -\xb7\x0a\x64\xa3\x1e\x9b\x80\x00\x59\xfd\x52\x29\xca\xcb\x0a\xab\ -\xa2\x13\x2e\x97\x93\xb7\x5e\x7d\x86\xe2\x89\x53\xb9\x6a\xf6\x3c\ -\xa2\x63\xf5\xf5\xe4\x71\x39\xec\xec\xd9\xb9\x85\x2d\x1b\xd7\xd0\ -\x50\x17\xb9\xa5\xb4\x8b\xf2\xb3\x79\xf8\x81\x05\x4c\x1c\xe7\x9f\ -\x88\x57\xd7\x9d\xe0\x2f\xcf\xbd\xc9\xfa\xb2\x5d\x41\xb6\x2c\x7c\ -\x89\x32\x38\x25\x7e\x4f\x13\xc1\x58\x15\x51\x6b\x15\x2a\xc7\xa4\ -\x8e\xa1\xd9\xa5\x63\x87\x85\x95\x00\x00\x20\x25\x15\x1b\xd7\xb0\ -\x77\xe7\x16\x2e\xbf\xea\x6a\xc6\x4d\xb8\x82\xa8\xe8\xc0\x2b\xca\ -\xb8\x5d\x4e\x8e\x1c\xdc\xcb\xde\x1d\x15\x1c\xd8\xb3\xdd\x50\x9f\ -\xf0\x9e\x12\x1f\x1b\xc3\xfd\xb7\x5f\xc3\xed\xf3\xa6\xfb\x95\x92\ -\xcb\xee\x70\xb1\xe8\xcd\x4f\x78\xf5\xed\x55\xb8\x3c\x7d\xb3\x28\ -\xcb\x29\xac\x06\xa7\x30\xeb\xf1\x0c\xc0\xaa\x1e\xb1\x4a\xab\xe5\ -\x08\x3a\x1e\x65\xcc\xbc\x62\x1c\x4b\x97\x19\x9b\x30\xe1\x52\xd8\ -\x3a\xdb\x59\xb5\xec\x4d\xd6\xad\xf8\x80\x21\x23\xc6\x30\x70\xc8\ -\x08\x72\xf2\x8a\x48\xeb\x97\xd5\xa5\x0b\x6b\x7b\x5b\x0b\x0d\x75\ -\x35\x1c\xaf\xab\xe6\xe8\xc1\xfd\xd4\x54\x1d\x42\x8d\x00\x17\xd6\ -\xae\x10\x42\x9c\x4e\xc9\x95\x99\xee\x5f\xa5\x99\xe5\xeb\xb6\xf0\ -\xf7\x17\xdf\x8e\xd8\xc4\xa3\x7a\x63\x74\x52\x13\x6f\x0f\xab\x62\ -\x09\xaf\xb2\xcd\xea\xf0\x70\x38\x56\x41\xa2\xd3\x3a\x60\xdc\x65\ -\x83\xc9\x4c\x4f\xe1\x78\x53\xab\x1e\xcd\x05\x05\xb7\xcb\xc9\xee\ -\x6d\x9b\xd9\xbd\x6d\x33\x00\x51\xd1\x31\xa4\x65\x64\x12\x1d\x15\ -\x43\x54\x4c\x0c\x16\x8b\x05\xbb\xad\x13\xbb\xad\x93\xce\x8e\x36\ -\x9c\x8e\xc8\x0c\xce\xb9\x14\x5a\x53\x72\x1d\xaa\xac\xe5\x89\x85\ -\x4b\x23\x36\xf1\x68\xb0\x30\x3a\x8e\x41\xf6\x70\x09\xd0\xea\xe5\ -\x0b\xeb\x94\x42\x61\x2f\xaf\xf1\x1e\x07\x74\xf1\x6d\x54\x14\xc1\ -\xec\x2b\x8b\x79\xf5\xdd\x55\x7a\x34\x17\x12\xdc\x2e\xa7\x6e\x89\ -\x16\xc3\x99\xb8\xd8\x68\xee\xbc\x69\x26\xf7\x2c\x98\xe3\xd7\x74\ -\xbf\xc3\x66\x67\xd1\xd2\xe5\x2c\x7e\x2f\x72\xeb\x0c\x04\x13\x97\ -\xdb\xd8\x25\x90\x2a\x03\xff\x9b\x44\x5b\x50\xbf\x33\x49\x34\x5a\ -\x01\x84\x90\x87\xa5\x14\xba\x39\x37\x5f\x3d\x35\xb2\x04\xa0\xb7\ -\x23\x84\x60\xee\xb4\x09\x7c\xeb\xee\x1b\x49\x4f\x4b\xee\xf6\x7a\ -\x55\x95\xbc\xbf\x62\x03\x4f\xbf\xf2\x3e\x2d\x6d\x1d\x21\xb0\x30\ -\x32\xf1\xb8\x0d\x9e\x01\xf4\xc0\xb5\x3a\x36\x8a\x4e\x38\x99\x11\ -\x48\x4a\x71\x04\x98\xa4\x8b\x55\xc0\x88\xc1\x45\x0c\x28\xcc\xe1\ -\x48\x55\x9d\x5e\x4d\x9a\x04\xc8\xb0\x81\x05\xfc\xe0\xc1\xf9\x8c\ -\x1d\xe1\x5f\xb6\xa4\x5d\xfb\x8f\xf2\xc4\xc2\xa5\xec\x3e\x50\x19\ -\x64\xcb\x22\x9f\x4e\x83\x2b\x07\xf7\x24\x29\x68\x8c\x45\x36\xc1\ -\x69\x01\xe0\xa0\x1f\x61\xdc\x9a\xb8\xe3\xfa\x69\xfc\xee\xe9\xc5\ -\xfa\x36\x6a\xe2\x37\x49\x09\xf1\x7c\xfd\xae\xeb\xb9\x65\xce\x15\ -\x28\x7e\x96\x70\x7f\xe5\xed\x15\x3c\xf9\xd2\x7b\x7d\xc2\x8b\x4f\ -\x0f\x8c\xce\xd3\x68\xb1\x6a\x13\x00\x01\xa4\xc4\x41\x6e\x92\x24\ -\x23\x4e\xbe\x07\x27\x05\x40\x08\xb9\x43\x2f\x5f\x80\x53\x5c\x3b\ -\x73\x22\xcf\xbc\xf6\x61\xc4\xe6\xae\x8f\x58\x84\x60\xe4\xd8\x52\ -\xe6\xdf\x72\x33\x63\xfb\x27\x22\x84\xff\x83\xf9\x48\x75\xbd\x39\ -\xf8\x35\xd0\x62\xb0\x00\xf8\x3b\x03\x88\xb1\xfa\x52\xdd\x0d\x48\ -\x97\x24\x9c\xbc\x45\x48\xb9\x1a\x4e\x0a\x80\xc5\x62\xd9\xae\x77\ -\x75\x99\x68\xab\x95\x1b\x67\x5f\xc1\x0b\x4b\x3e\xd6\xb5\x5d\x93\ -\x4b\x93\x93\x5f\xc4\xec\x79\xb7\x91\x9b\xdf\x1f\x27\xb0\xa9\x0a\ -\x92\x62\x04\xc5\xf9\x2a\xe9\xc6\xe7\xd8\xec\x55\x38\x9c\x2e\xc3\ -\x73\x39\x58\x2d\x5d\x67\xb0\x4d\x8c\xf6\xa5\xb3\x3b\x95\xed\xea\ -\x6c\xbc\x8a\x65\x3b\x9c\x14\x80\xa6\x6c\xf6\xa4\xd4\xe2\x04\x02\ -\xf7\x8e\xb9\x08\xb7\x5e\x7b\x25\xaf\xbe\xbb\x12\xa7\x2b\x72\x1d\ -\x65\x22\x81\xb8\x84\x04\xae\x9a\x7d\x23\x63\x4a\x26\x5f\xe0\xcf\ -\xd0\xee\xf4\x65\x32\x1a\x92\x21\x19\x99\x25\xd1\x7b\xa9\xd7\x57\ -\xa9\x3f\xae\x4b\x4a\xae\x1e\x31\xae\xd0\x4a\x56\xa6\xa4\xae\x43\ -\xd0\x76\xb2\x52\x97\x10\x90\x11\x0f\x03\xd3\x25\x79\x49\x97\xfc\ -\x7b\x3b\x0e\xe5\xb2\x1f\x4e\x0a\xc0\x4c\x21\x3c\xe5\x35\xde\xdd\ -\x80\x7f\x59\x1e\xfc\x24\x3d\x2d\x99\x3b\x6e\x98\xce\xa2\xa5\x9f\ -\xea\xd9\xac\xc9\x49\x84\x50\x18\x37\x71\x0a\x53\x67\xcd\xeb\xb2\ -\x4c\xbb\x94\xb0\xff\x84\xa0\xc9\x2e\x98\x58\xa0\x12\x1b\x19\xf5\ -\x53\xc2\x9a\xba\x30\x10\x80\x82\xb4\x68\x06\x67\x49\x2e\xcb\xf2\ -\x55\xea\xb2\xbb\x20\x3e\x0a\xfc\x38\xe1\xdd\x79\x87\x10\x5e\x80\ -\x33\xbb\x43\x82\x6d\xc1\x30\xf2\x9e\x5b\x66\xfb\x9d\x1e\xda\xc4\ -\x7f\xf2\x0b\x07\x72\xcf\x37\x1e\xe1\xea\x79\xb7\x77\x39\xf8\xcf\ -\xa6\xb1\x13\x56\x1e\x54\x38\xd1\x69\x4e\x03\x7a\x4a\x75\xdd\x09\ -\xa3\x4d\x20\x2d\xe5\x4c\x95\xa5\x28\xc5\x97\xd9\xda\x1f\xef\x64\ -\xc1\x99\xb1\x7e\x5a\x00\x84\x24\x28\xa9\x6a\xe2\xe3\x63\x79\xe0\ -\x8e\xb9\xc1\x68\xba\x4f\x12\x9f\x98\xc4\x75\xf3\xbf\xc2\x9d\x0f\ -\x7e\x9f\xac\x5c\x6d\x69\xd8\x5a\x9a\x1b\xa9\x28\xdb\xc4\xba\xa3\ -\x67\xd2\xa1\x9b\x04\x86\xd1\x19\x9c\x15\x45\x21\x35\x39\xb0\x64\ -\x20\x52\x72\x3a\x79\xe7\xe9\xc9\xa0\x2a\x94\x0d\x42\x06\x27\xbd\ -\xd1\x4d\x73\xae\xe0\xed\x4f\x3e\xef\xd5\x19\x62\x82\x8d\xa2\x28\ -\x14\x5f\x7e\x15\x57\xce\xbc\x4e\x73\x44\xa3\xc7\xed\x66\xc3\xda\ -\x4f\xd9\xb4\x76\x39\xc3\xc7\x94\x30\x72\xdc\x44\xb6\x1e\x13\xe4\ -\x5c\x7a\x8d\x68\xd2\x0d\x87\x0d\xf6\x71\x49\x4d\x4e\xf0\xfb\x78\ -\xf7\x7c\x84\x50\xbe\x38\xf5\xff\xa7\x05\x20\xb9\x93\x8d\xed\xf1\ -\xfa\x6f\x04\x82\x2f\x7d\xd2\x4f\xbf\x7b\x17\x0f\xfd\xe8\x09\xd3\ -\xa5\x34\x00\x0a\x07\x0c\x61\xf6\xf5\xb7\xd2\x2f\x80\x54\xd4\xfb\ -\xf7\x6c\x67\xd5\x87\x4b\x2f\xc8\x4a\xe4\xf0\xf8\xf2\x16\x26\x18\ -\x1b\xd2\x1e\x91\xa8\xaa\xca\xfe\xa3\xc6\x86\x7e\x9f\x3d\xfd\xd7\ -\x88\xb3\xc5\xc5\xe9\xac\x3d\xa7\x05\x60\xe8\x50\xe1\x2c\xaf\xf1\ -\x54\x80\xf0\x2f\x91\x9e\x46\x86\x0e\xc8\xe7\xfe\xdb\xaf\xe1\x5f\ -\xff\xfe\x30\x18\xcd\xf7\x4a\x12\x12\x93\x99\x3e\xe7\x26\x46\x8e\ -\x2b\x45\xeb\xab\xba\xb9\xe9\x04\x2b\x3f\x58\xc2\xa1\xfd\x97\x8e\ -\xd7\x6f\x77\x0a\x12\xa2\xcd\x73\x7f\xad\xec\x3f\x5c\x63\xf8\x11\ -\x60\x46\x80\x55\x96\x05\x72\xd3\xcc\x81\xe2\x74\xea\xdb\x73\xf7\ -\x83\x85\x58\x87\x24\x28\x02\x00\x70\xf7\x2d\x57\xb3\x7e\xf3\x6e\ -\x76\xee\x3f\x12\xac\x2e\x7a\x05\x8a\xc5\x42\xc9\xe4\xe9\x5c\x39\ -\xe3\x5a\xcd\x79\x0b\xdc\x2e\x17\x5f\x7c\xf6\x31\x9b\xd7\xaf\xec\ -\x36\x5c\xb4\xd9\x0e\x39\x91\x53\xad\x3b\x6c\xd8\xbe\xf7\x88\xd1\ -\x26\x90\x93\x19\x58\x3d\x4e\x89\x58\x77\xf6\xbf\xad\xe7\xfe\x50\ -\xae\x13\x88\x47\x7a\x60\x57\x97\x58\x2c\x0a\xbf\x7e\xf4\xab\x7c\ -\xfd\xd1\x27\xcc\x98\xf2\x4b\x50\x38\x70\x08\x57\xcf\xbb\x9d\x8c\ -\xcc\x1c\xcd\xf7\x1e\xdc\xbb\x83\x15\x1f\x2c\xf1\x3b\x09\xe9\xe1\ -\x26\xc1\x80\x93\xd5\x6e\x4c\xfc\xa7\x6c\xbb\xf1\xb5\x0c\x8b\xf2\ -\x03\x2b\x0d\x2e\x91\x97\x16\x00\xab\x62\x59\xa7\xb7\x47\xe0\xf9\ -\x64\xa4\x26\xf3\xdb\x1f\x3d\xc8\xb7\x7f\xf6\xd7\x3e\x95\x40\xb2\ -\x3b\x12\x93\x53\x98\x36\xfb\x46\x46\x8e\x9f\xa8\xf9\xde\xe6\xc6\ -\xe3\xac\xf8\x70\x09\x87\xf7\xef\xd6\x74\x9f\xcb\x0b\x1b\xaa\x14\ -\xae\x1a\xa0\x62\x70\x6e\x8b\x88\xc1\xed\xf1\xb2\x79\xc7\x7e\xa3\ -\xcd\xa0\x28\x3f\xa0\xe0\x5d\x29\x14\xcb\x17\x67\x7f\x70\x8e\x00\ -\x8c\xcb\x11\x0d\xe5\x35\xde\x9d\x80\x7f\x79\xa0\x03\x64\xd8\xc0\ -\x02\xfe\xe7\x3b\x5f\xe1\xe7\x7f\x7c\xc1\xb8\xc2\x0a\x61\x82\xc5\ -\x62\xa5\xf4\xca\x99\x4c\xbe\xea\x1a\xa2\xa2\xb5\xed\xc8\xb9\x5d\ -\x4e\xd6\xaf\xfa\x88\xb2\x2f\x56\x05\x9c\xa1\xa8\xc5\x0e\xdb\x8e\ -\x09\x8a\xf3\xcd\xbd\x00\x7f\xd8\xba\xeb\xa0\xe1\xeb\x7f\x80\xa2\ -\xbc\x80\x04\x60\x5b\x49\xae\x38\x27\x7b\xeb\x05\x3e\x61\x12\x3e\ -\x14\x41\x16\x00\x80\x19\x93\xc7\xf2\xd8\x0f\xee\xe1\x97\x7f\x7e\ -\xc9\xf0\xcc\x2a\x46\x51\x34\x68\x18\x57\x5f\x7f\x1b\xe9\x99\xda\ -\xa7\x73\x07\xf7\xee\x60\xf9\xfb\x6f\xe8\x52\x25\xb6\xb2\x45\x30\ -\x2c\xd3\x14\x00\x7f\x58\xbe\xbe\xc2\x68\x13\x88\xb2\x5a\xc8\xf5\ -\x33\x6b\xf3\x79\x5c\xb0\x03\x7f\x81\x00\x08\xa9\x2e\x43\x28\xff\ -\x19\x48\xeb\x5a\x99\x35\x65\x3c\x42\x11\xfc\xe2\x89\x45\x7d\x4a\ -\x04\x52\xd2\x32\x98\x79\xed\x7c\x86\x8c\x18\xa3\xf9\xde\x13\x0d\ -\xc7\x58\xfe\xfe\x1b\x54\x1d\x39\xa0\x9b\x3d\x12\x68\x73\x9a\x0e\ -\x01\xdd\xe1\xf6\x78\x59\xfd\x45\x50\x1c\x66\x35\x51\x98\x97\x1d\ -\x58\x3e\x42\x45\x59\x76\xfe\x47\x17\x08\x40\x4c\xab\x75\x8d\x23\ -\x55\x6d\x17\x10\x92\xfd\xe1\x99\x93\xc7\x11\xff\xa3\x18\xfe\xdf\ -\x13\x2f\xd2\x61\xeb\x5d\xb9\xf7\xce\xc7\x1a\x15\xc5\xe5\x53\x67\ -\x73\xf9\x95\x57\x6b\x4e\xe6\xe0\x72\xd8\x59\xb7\x6a\x19\x15\x1b\ -\x3e\x0b\xca\xb2\x29\xc6\x62\xce\x00\xba\xe3\xf3\xb2\x5d\xb4\x75\ -\x18\x5f\xe7\x70\xd4\xb0\xae\x0b\xdd\x5c\x82\x36\xb5\x86\xf5\xe7\ -\x7f\x78\x81\x00\x8c\x1a\x25\x5c\xe5\x35\xea\x4a\x90\x37\x05\xd2\ -\x4b\x20\x4c\x2a\x1e\xc1\x33\xbf\xfb\x21\x3f\x7e\x7c\xa1\xe1\x1e\ -\x56\xc1\x62\xe8\x88\x31\xcc\xb8\x76\x3e\x29\x5a\x6b\x39\x49\xc9\ -\xae\xad\x9b\x59\xfd\xe9\x3b\x74\xb6\x07\xaf\xee\x62\x4f\x6b\x22\ -\xf6\x05\xde\xfa\x78\x5d\xf7\x17\x85\x80\x51\x43\x07\x68\xbf\x49\ -\x8a\x4f\x4b\x4b\xc5\x05\x61\xb9\x17\x8f\x0b\x93\x72\x19\x82\x90\ -\x09\x00\x40\x41\x4e\x3f\x9e\xfe\xcd\x0f\x78\xe2\x99\x25\x7c\xb8\ -\x6a\x53\x28\xbb\x0e\x2a\x85\x03\x86\x30\xf5\xea\x1b\xfc\xaa\x51\ -\x78\x3e\x0d\x75\x35\x2c\x7f\xff\x0d\x6a\x2a\x0f\x05\xc1\xb2\x73\ -\xd9\x7e\x4c\xd0\x68\x33\x45\xe0\x52\x54\xd6\x36\xb0\x69\x9b\xf1\ -\xc7\x7f\x00\xa3\x86\xf5\xd7\x7c\x8f\x50\xd4\x0b\xa6\xff\x70\x09\ -\x01\x50\xa4\xf2\x8e\x2a\xd4\xbf\x71\x76\xb4\x60\x08\x88\x8f\x8d\ -\xe1\x7f\xbe\x73\x17\x73\xa6\x4e\xe0\xf1\xa7\x16\x53\x7f\x22\x32\ -\x8b\x6a\x22\x04\x03\x06\x0d\x67\xe2\x95\xb3\xe8\x3f\x78\xb8\xe6\ -\xdb\x9d\x0e\x1b\x6b\x57\x7c\xc0\x96\x8d\xeb\x90\x41\x8a\xcf\x38\ -\x1f\xc9\xb9\x55\x8d\x4d\xce\xe5\xf9\x37\x3e\x0e\x8b\x6c\x49\x09\ -\xf1\x71\xf4\xd7\xee\x03\xa0\x7a\x5d\x96\xf7\x2e\xf6\x83\x8b\x0a\ -\xc0\xf8\x42\x51\x53\x51\xe3\x59\x2f\x11\x53\xb5\xf6\xa4\x07\x97\ -\x8f\x1f\xce\xcf\x7f\xfa\x28\x8b\x96\xae\xa0\xfc\x8b\x55\xb8\x7a\ -\x58\x04\x31\x54\x44\xc7\xc6\x31\x7a\xdc\xe5\x8c\xbf\x7c\x2a\xe9\ -\xfd\xb4\x1f\xd3\x48\x29\xd9\xb9\x65\x03\xab\x3f\x7e\x17\xbb\xcd\ -\xcc\xc6\x1b\x2e\x1c\xa9\xaa\xe3\xd3\xb5\xc6\xef\xfe\x03\x8c\x1e\ -\xde\x1f\xe5\xfc\xf4\x3e\xdd\x20\x85\x58\x5d\x3a\x40\x5c\x34\x7c\ -\xf1\x92\xa9\x21\x24\x62\x31\x60\x88\x00\x78\x55\xa8\xe9\x88\x65\ -\xea\xec\xeb\x99\x70\xc5\x34\x36\x7c\xf6\x09\x5b\x37\xaf\xc7\xed\ -\x0e\xbf\x57\x94\xd5\x1a\xc5\x80\x21\x23\x18\x31\xaa\x98\x41\x23\ -\x46\x13\x1d\x60\xc9\xb1\xfa\xda\x2a\x3e\x7d\xff\x75\x8e\x55\x1f\ -\xd5\xd9\x42\x93\x9e\xb2\xf0\xb5\x65\x61\xe3\xaf\x32\x69\xfc\x65\ -\x9a\xef\x11\xaa\xbc\x64\x76\xde\x4b\x0a\x80\x27\x4a\x79\xdd\xea\ -\x56\x9f\x00\x42\x5e\x00\xed\x78\xa7\xc0\x75\xf2\x54\x30\x2e\x3e\ -\x91\x19\xd7\xce\xe7\x8a\x19\xd7\xb2\x73\xcb\x26\xb6\x96\xad\xa3\ -\xb1\xc1\xd8\x8d\xc2\xa8\xe8\x18\xfa\x0f\x1a\xc6\xf0\x51\xe3\x19\ -\x3c\x7c\x34\xd1\x31\xb1\x01\xb7\xe5\xb0\x75\xb2\x66\xf9\xfb\x6c\ -\x2b\x5b\x1f\x16\x53\x4c\x93\x73\xd9\x77\xb8\x9a\x55\x1b\x8c\x3f\ -\xfa\x3b\xc5\x94\x09\x23\xb5\xde\xe2\xb5\x58\x95\xa5\x97\xfa\xe1\ -\x25\x05\xe0\xf2\x2c\x51\x57\x5e\xeb\x59\x83\x14\x33\xb4\xf6\xd8\ -\x53\x5a\x1d\x17\x7e\x16\x13\x1b\x47\xc9\xe4\x69\x94\x4c\x9e\x46\ -\x5d\x6d\x25\x07\x76\xef\xe0\xc0\xde\xed\x9c\xa8\x0f\x7e\x8e\x81\ -\xa8\xe8\x68\xf2\x0b\x07\x51\x34\x70\x28\x85\x03\x87\x92\x9d\x57\ -\x18\x70\x2c\xf6\x29\xa4\x94\x6c\x2b\x5b\xcf\x9a\xe5\xef\xe3\xb0\ -\x19\x9b\x5d\xd6\xe4\xe2\xa8\xaa\xe4\x2f\xcf\xbd\x15\x36\xc2\x5c\ -\x94\x97\x45\x41\x4e\x3f\x8d\x77\x89\x15\xe3\x72\xc4\x25\xab\xf5\ -\x76\x9d\x1d\x4e\x15\x8b\x11\xcc\xd0\xd8\x63\x8f\x89\xea\x66\xce\ -\x91\x93\x57\x44\x4e\x5e\x11\x53\x67\x5f\x4f\x7b\x5b\x0b\xb5\x55\ -\x47\x38\x56\x75\x98\xba\x9a\x2a\x1a\x4f\xd4\x07\xbc\x7e\x16\x42\ -\x21\x35\x2d\x9d\x8c\xac\x5c\xd2\x33\xb3\xc9\xcc\xca\x25\x3d\x33\ -\x87\x7e\x59\xb9\x58\x74\x2c\x05\x7d\xac\xfa\x28\x9f\xbe\xff\x3a\ -\xf5\xb5\x55\xba\xb5\x69\xa2\x3f\x6f\x7c\xb8\x86\x2d\xbb\xc2\xa7\ -\x1e\x62\x00\x6f\x7f\x40\x7d\xad\xab\x9f\x76\x29\x00\x6e\x94\xc5\ -\x51\xa8\x7f\x04\x02\x9f\xe3\x06\x40\x6e\x92\x64\x57\xbd\xc0\xe3\ -\xc7\xb2\x2b\x29\x39\x95\xe1\xa3\xc6\x33\x7c\xd4\x99\x7c\xa6\x4e\ -\x87\x9d\x96\xa6\x13\x74\x76\xb6\xe3\xe8\xec\xc4\x66\xef\x44\x3d\ -\x19\x1a\xeb\xb0\xdb\x89\x8e\x8e\xc1\x6a\xb5\x12\x1d\x1b\x4b\x4c\ -\x4c\x1c\x09\xc9\xc9\x24\x24\x24\x91\x94\x92\x86\x55\x63\xb1\x05\ -\x2d\xd8\x6d\x1d\xac\xfe\xf8\x5d\x76\x6e\xd9\x10\x36\x6f\x15\x93\ -\x8b\x53\x75\xec\x38\xff\x7c\xe5\x7d\xa3\xcd\x38\x87\xa9\x13\x47\ -\x6b\xbd\xc5\x6e\xf5\x58\x2e\x39\xfd\x87\x6e\x04\x60\x52\x81\x68\ -\x2c\xaf\xf1\xbe\x05\x7c\x59\x6b\xcf\x3d\x21\x2e\x0a\x2e\x2f\x94\ -\x7c\x71\x54\x10\xc8\xd6\x4b\x4c\x6c\x1c\xd9\x79\x85\xba\xdb\xd5\ -\x13\x6c\x1d\xed\x2c\xfc\xeb\xaf\x71\x3a\x8c\xf7\x24\x33\xe9\x1a\ -\x55\x95\xfc\xe6\x1f\xaf\xe2\x70\x86\xcf\xa6\x73\x5e\x76\x06\xe3\ -\x2e\xf3\xaf\xbc\xdb\x69\x04\xaf\x8f\xed\x2f\xba\x0c\x16\xe9\x76\ -\x21\x2b\xa5\xfa\x2f\x6d\xbd\xea\x43\x56\xa2\x64\x68\x2f\x0a\x50\ -\x71\xb9\x5d\xe6\xe0\x8f\x10\x9e\x79\xed\x03\xb6\xed\x0e\xbe\xf3\ -\x95\x16\xe6\x4e\x2b\xbd\xa0\xe6\x43\x77\x48\x94\x67\xba\xbb\xa6\ -\x5b\x01\x28\xc9\xb7\xae\x04\x0c\x09\x80\x8e\x35\x13\x55\x98\x84\ -\x98\x65\xab\x37\xf3\xe2\x92\xf0\xab\x63\x31\xe7\xaa\x12\xad\xb7\ -\xec\x2b\xc9\x65\x6d\x77\x17\x75\x2b\x00\x42\x08\x89\xe0\x59\xad\ -\xbd\xf7\x94\x36\x27\xec\x69\x30\x5d\x53\x4d\x42\xc7\xd6\xdd\x07\ -\xf9\xdd\x93\x5d\xee\x99\x19\xc2\xd8\xcb\x06\x69\x8e\xff\x17\xf0\ -\x2f\xe1\x47\x61\x48\xbf\xce\xb2\xa2\x2c\xca\x73\x08\x42\x56\xdf\ -\xab\xc5\x0e\x6b\x0f\x2b\x38\xbb\x4e\x69\x67\x62\xa2\x1b\x47\xab\ -\xeb\xf9\xc9\xef\x9f\xc3\xe5\x09\xbf\x87\xee\xd6\x6b\xaf\xd2\x7a\ -\x8b\x4b\xb1\x28\x2f\xfa\x73\xa1\x5f\x02\x30\x26\x5b\xd4\x0b\x95\ -\x90\x48\x63\xbb\x13\xd6\x1d\x51\x4e\x3b\x02\x99\xf4\x3d\xf6\x1c\ -\x17\x84\xf2\x90\x64\xdf\xe1\x6a\xbe\xfd\xf3\xbf\xd1\xda\x16\x7e\ -\xfe\x18\x79\xd9\x19\xcc\x98\x3c\x56\xd3\x3d\x12\x5e\xe9\xea\xec\ -\xff\x6c\xfc\xf6\x66\x51\xa5\xf2\x7b\x5f\xdb\xc1\xa5\xaa\x55\xe0\ -\x0e\x0f\xaf\x4b\x13\x83\xa8\x6c\x11\x6c\xa8\x12\x21\x79\x09\xec\ -\x3e\x50\xc9\x0f\x7e\xf9\x14\x2d\x6d\xe1\x19\x7b\x71\xc7\xbc\xe9\ -\x9a\x93\x7f\x08\xa9\xfc\xc9\xdf\x6b\xfd\x6e\x79\x42\xa1\xd8\x06\ -\x62\xb9\x26\x4b\x02\xa0\x3b\x27\x20\x93\xbe\x41\x5d\xbb\x60\xe5\ -\x01\x85\x23\xcd\x81\x1d\x05\xfb\xc3\x86\x8a\x3d\x7c\xff\xb1\x7f\ -\xd0\xd6\x1e\x7e\x6f\x7e\x80\xe4\xc4\x78\xae\x9f\x75\xb9\xc6\xbb\ -\xc4\xb2\x92\x02\xb1\xd5\xdf\xab\x35\xd5\x89\x95\xc2\xfb\x07\x21\ -\x95\xab\x35\x5a\xa4\x89\xec\x04\xc9\x4e\xcc\xcd\x3f\x13\xb0\x7b\ -\x60\x4b\xad\x60\x47\x9d\x20\x21\x06\xa2\x15\x49\x94\x05\xfa\xa7\ -\x41\x76\x62\xe0\x93\x51\x29\x25\xaf\xbc\xbd\x82\xa7\x5f\x79\x1f\ -\x55\x0d\xdf\xa3\xe6\x2f\xdf\x38\x93\xf8\x58\x8d\xc1\x65\xd2\xfb\ -\x7f\x5a\x2e\xd7\x24\x00\x13\xf2\xa2\x96\x95\xd7\x78\xb7\x02\xe3\ -\x34\x19\xa5\x81\xe4\x58\x48\x8a\xf1\xed\x05\x98\x98\x00\x78\x54\ -\x68\xb5\x03\x27\x5f\x0c\x75\xed\x30\x63\xb0\x24\x39\x80\xc0\xcb\ -\xd6\xb6\x4e\x7e\xf1\x97\x45\x6c\xdc\xb2\x57\x57\x1b\xf5\xa6\x5f\ -\x5a\x0a\xb7\xcf\x9b\xa6\xf5\xb6\x6d\xc5\xf9\xd6\x15\x5a\x6e\xd0\ -\x1c\xd1\x22\xa4\x7c\x42\xeb\x3d\x5a\xc9\x4f\x09\x5f\x55\x36\x31\ -\x1e\x55\xc2\xfe\xe3\xda\x67\x89\x9f\xae\xab\xe0\x9e\x47\x7e\x17\ -\xf6\x83\x1f\xe0\x81\x2f\x5d\x4b\x5c\xac\xb6\x34\xf1\x42\xc8\xc7\ -\xfd\x39\xfa\x3b\x1b\xcd\x02\xb0\x3f\xdf\xf2\x92\x84\xa0\x7e\x83\ -\xfd\xd3\xa4\x5f\x75\xce\x4d\xfa\x2e\xd5\x6d\xc2\xef\x63\xe2\xda\ -\xfa\x46\x1e\xf9\xdf\xa7\x79\xec\x89\x17\x69\x6a\x0e\x5e\x5e\x45\ -\xbd\x28\xca\xcb\x62\xde\x4c\xad\x6b\x7f\xf6\xed\xcf\xb5\x68\x3e\ -\xa9\xd3\x2c\x00\x77\x08\xe1\x55\x84\xfc\x95\xd6\xfb\xb4\x10\x67\ -\x85\xab\x06\xa8\x24\x87\x34\x04\xc9\x24\x92\x90\x12\xea\x3b\xba\ -\x9e\x05\x34\xb7\xb6\xf3\xf7\x17\xdf\xe1\x9e\x87\x1f\x67\x43\xc5\ -\x9e\x10\x59\xd6\x33\x84\x10\xfc\xe7\x43\xb7\x05\xb0\xf3\x2f\x7f\ -\x7a\x87\x10\x9a\xcf\x4d\x34\xed\x01\x9c\x62\x7c\xae\xe5\xdf\x15\ -\xb5\xea\x7f\x03\xda\x0e\x28\x35\x90\x12\x0b\x33\x06\xab\xec\xae\ -\x17\xec\x3f\x61\x6e\x0a\x9a\x5c\x48\x43\x07\x14\xa5\x5e\xf8\x79\ -\x73\x6b\x3b\xaf\xbe\xbb\x9a\x25\x1f\xae\x09\xab\x80\x1e\x7f\x98\ -\x37\xeb\x72\x4a\x46\x0f\xd5\x74\x8f\x94\xec\x28\xce\xb7\x2c\x09\ -\xa4\xbf\x80\x04\x40\x08\xa1\x96\x57\x7b\x7e\x85\x10\xaf\x07\x72\ -\xbf\xbf\x28\xc0\xa8\x6c\x89\xc3\xed\xf3\x0f\x30\x31\x39\x9b\x56\ -\x87\xe0\x94\x6b\x8a\xaa\x4a\xca\x77\xec\xe7\xc3\xd5\x9b\x58\xf5\ -\xf9\x56\x9c\xae\x90\x39\xae\xea\x46\x7a\x5a\x32\xdf\xbe\x57\x7b\ -\x32\x6e\x45\xc8\x9f\x0a\x21\x02\x3a\x2d\x0d\x48\x00\x00\x8a\xf3\ -\x2d\x4b\x2a\x6a\xd5\x2d\xc0\xf8\x6e\x2f\xee\x21\x63\xf3\x24\x8d\ -\x36\x81\x2d\xf2\xfe\xa6\x26\x41\xa4\xcd\xe6\x66\xe3\xd6\x03\x6c\ -\xa8\xd8\xcd\xca\xcf\xb7\x46\x7c\xc5\xe9\x47\x1e\xbc\x95\xa4\x84\ -\x78\xad\xb7\x95\x8d\xcf\xb3\xbc\x13\x68\x9f\x01\x0b\x80\x10\x42\ -\x96\x57\xbb\xff\x13\xa1\x04\x3d\x74\x2a\x4a\x81\xf1\x79\x92\xf5\ -\x47\xcd\x59\x40\x5f\x45\x4a\x95\xb6\x96\x66\xea\x8f\x55\xd3\x70\ -\xac\x86\xfa\x63\x95\x54\x1f\x39\x18\x96\x89\x62\x03\xe1\xe6\x39\ -\x53\x98\xae\xd1\xe5\x17\x40\xa0\xfe\x58\x88\xc0\xcb\x3a\x05\x2c\ -\x00\x00\x25\x05\x51\xcb\xcb\x6b\xd4\x77\x41\xde\xd8\x93\x76\xfc\ -\x21\x2b\x51\x92\x9f\x02\x35\xe6\x52\xa0\xd7\xf3\xe1\xd2\x97\x4e\ -\x97\x4e\xb3\x77\x76\xd0\xd9\xd1\x8e\xad\xb3\xbd\xd7\x66\x51\x1a\ -\xd2\x3f\x9f\xef\x3f\x70\x8b\xf6\x1b\xa5\x58\x5a\x5c\x10\xf5\x49\ -\x4f\xfa\xee\x91\x00\x00\xa8\x5e\xf1\xb0\x62\x91\xd7\x00\x81\xe5\ -\xc3\xd6\xc0\x98\x1c\x49\x7d\x87\xc0\x63\x06\x0a\xf5\x6a\x42\x51\ -\x09\x29\x5c\x88\x8f\x8d\xe1\x97\x3f\xbc\x87\x68\x8d\xb5\x22\x01\ -\x97\x90\xe2\x47\x3d\xed\xbf\xc7\x95\x7f\x4a\x8b\xc4\x41\x09\x7f\ -\xed\x69\x3b\xfe\x10\x6b\x85\x11\xbd\x28\x4b\x90\x49\xdf\x46\x51\ -\x14\x7e\xf6\xfd\xbb\x29\xd2\x5e\xe9\x07\x24\x7f\x28\x2e\x14\x3d\ -\x4e\xd4\xa3\x4b\xe9\x2f\x7b\xb4\xf2\x4b\x20\x24\xc9\xfa\x07\x65\ -\x48\x92\x82\x3e\xd7\x30\x31\x09\x3e\xdf\xbd\xff\x66\xae\xd2\x9e\ -\xe8\x13\xa0\xde\x15\xa7\xfc\x56\x0f\x1b\x74\x11\x80\xa9\x99\xa2\ -\x5d\x22\x7f\xa2\x47\x5b\xdd\xa1\x00\xa3\xb3\xcd\x59\x80\x49\x64\ -\x73\xf3\x9c\x29\xdc\x7e\xbd\x66\x5f\x7f\x1f\x42\xfe\xd7\xe4\x0c\ -\xa1\x8b\x4b\xa3\x6e\xc5\x3f\x4b\xf2\x2c\xcf\x83\x08\x49\x32\xb5\ -\xec\x24\x49\xbf\x78\x53\x04\x4c\x22\x93\xab\xaf\x2c\xe6\x91\xaf\ -\xdf\x1a\xd0\xbd\x52\x88\x95\xc5\xb9\x96\x97\xf4\xb2\x45\x37\x01\ -\x10\x42\x48\x8b\x45\x3c\x04\x84\x24\xb8\x7a\x54\x8e\x34\x83\x86\ -\x4d\x22\x8e\x19\x93\xc7\xf2\xb3\xef\xdd\x1d\x68\x65\x29\x9b\xf4\ -\x88\xaf\x6b\x0d\xf8\xe9\x0a\x5d\xcb\x7f\x8f\xcb\x11\x87\x81\xa0\ -\xc6\x09\x9c\x22\x2d\x0e\x72\x92\xcd\x59\x80\x49\xe4\x30\x7d\xf2\ -\x58\x1e\x7b\xf8\x5e\xcd\x7e\xfe\xa7\x91\xfc\xbc\xb4\x48\xe8\x5a\ -\xaa\x48\x57\x01\x00\x68\xcd\x53\xfe\x00\x94\xe9\xdd\xee\xc5\x18\ -\x99\x25\xd1\x98\x2a\xdd\xc4\xc4\x10\xe6\x4e\x2f\xe5\x17\x0f\xdf\ -\x8b\x35\xf0\x12\x73\x5b\x5a\xf3\x95\x3f\xeb\x69\x13\x04\x41\x00\ -\x66\x0a\xe1\x51\x85\xf2\x50\x28\xb2\x08\x27\xc5\xf8\x42\x87\x4d\ -\x4c\xc2\x99\xdb\xaf\x9f\xc6\x4f\xbf\x73\x57\x4f\x06\xbf\x0b\xa9\ -\xdc\x3f\x53\x08\xdd\x53\x16\xeb\x2e\x00\x00\xa5\x79\xa2\x5c\xc0\ -\x63\xc1\x68\xfb\x7c\x46\x66\x49\xa2\xcd\xdc\x01\x26\x61\x88\xc5\ -\xa2\xf0\x5f\x0f\xdd\xce\xf7\x1f\x98\xaf\xb9\xaa\xcf\xd9\x48\xf8\ -\x99\x96\x3c\x7f\x5a\x08\x8a\x00\x00\x8c\xcf\x55\x7e\x2b\x85\x58\ -\x19\xac\xf6\x4f\x11\x6d\xf1\x89\x80\x89\x49\x38\x91\x9a\x9c\xc8\ -\x1f\xfe\xe7\x1b\xdc\x7c\xcd\x94\x1e\xb6\x24\x3f\x3b\xe8\x5b\x56\ -\x07\x85\xa0\x09\x80\x10\x42\xb5\xaa\xe2\x5e\xa0\x29\x58\x7d\x9c\ -\xa2\x7f\xba\x24\xd7\xdc\x10\x34\x09\x13\x2e\x1b\x52\xc4\x33\x8f\ -\x3f\x4c\xe9\xd8\x61\x3d\x6d\xaa\x45\x15\x96\x7b\x02\x49\xf4\xe1\ -\x2f\x41\x13\x00\x80\x71\x05\xa2\x1a\x29\xbf\x11\xcc\x3e\xc0\x97\ -\x2a\xb2\x38\x4f\x92\xa0\x2d\x85\x9a\x89\x89\xae\x28\x8a\xe0\xae\ -\x9b\x67\xf1\xe4\xff\x7e\x8f\x9c\xcc\xf4\x1e\xb7\x27\x91\xff\x51\ -\x9a\x27\x2a\x75\x30\xed\x92\x04\x55\x00\x00\x4a\x0a\xac\x6f\x20\ -\x78\x21\xd8\xfd\x44\x5b\xa0\xb4\x40\x0d\xfe\x2f\x14\xe1\xc4\xc6\ -\xc6\x19\x6d\x42\xaf\x24\x2f\x3b\x83\xbf\xfd\xe2\x3b\x7c\xeb\x9e\ -\x1b\x7b\xb2\xd9\x77\x1a\x09\xff\x9a\x90\x6f\x0d\x7a\x35\xae\x1e\ -\x47\x03\xfa\x43\xab\x53\xf9\x8f\x94\x68\x75\x0c\xa0\xb9\xc4\xa9\ -\x16\xd2\xe2\x60\x68\xa6\x64\x6f\x00\x19\x63\x7b\x3b\x71\x09\x09\ -\x4c\x9b\x7d\x23\xa3\x4b\x26\x1b\x6d\x4a\xaf\x42\x51\x04\x37\xcf\ -\x99\xc2\x37\xef\xb9\x51\x7b\x0e\xff\x4b\xb3\xc5\xa9\x2a\xdf\xd7\ -\xab\xb1\xae\x08\x89\x00\xcc\x1c\x28\x1c\xe5\xb5\x72\x01\x52\x2d\ -\x03\x32\x82\xd9\xd7\xd0\x0c\xc9\xc1\x46\x81\xc7\x2c\x2f\x06\xf8\ -\x92\x4c\x5e\x36\xb6\x94\x99\x73\xe7\x13\x97\x90\x60\xb4\x39\xbd\ -\x8a\xa1\x03\xf2\x79\xe4\xa1\xdb\x18\x3d\x6c\x80\x9e\xcd\x36\x59\ -\x2c\xca\x82\x29\xf9\xc2\xae\x67\xa3\x97\x22\x24\x02\x00\x50\x92\ -\x27\x8e\x56\xd4\xb8\xef\x94\x28\x1f\x02\x41\x3b\xb8\xb3\x5a\x20\ -\x3d\x5e\xd2\xd0\x4d\xc6\xd8\xbe\x40\x5e\xe1\x00\x66\xcf\xbb\x9d\ -\xec\xdc\x02\xa3\x4d\xe9\x55\x24\x27\x25\xf0\xb5\x2f\x5f\xc7\x2d\ -\x73\xae\x08\xd4\xa5\xf7\x52\xa8\x52\xa8\x5f\x19\x97\x63\x39\xac\ -\x67\xa3\x5d\x11\x32\x01\x00\x28\xce\x8f\xfa\xa4\xa2\xc6\xfb\x2b\ -\x19\x64\x1f\x81\x7e\x09\xbe\x8c\xb1\x7d\x95\xd8\xf8\x04\xae\x98\ -\x3e\x97\x92\x49\xd3\x7a\x74\xfe\x6c\x72\x2e\x71\xb1\xd1\x2c\xb8\ -\xf6\x2a\xee\x59\x30\x9b\xc4\x78\xfd\xf7\x52\x84\xe0\xa7\x25\x79\ -\x51\xcb\x74\x6f\xb8\x0b\x42\x2a\x00\x00\xe3\xf3\x94\x5f\x55\xd4\ -\xca\x62\x90\x37\x07\xab\x8f\x82\x64\xdf\x3e\x80\xb7\x8f\x2d\x03\ -\x14\x45\x61\xfc\xc4\xab\xb8\x72\xd6\x75\xc4\x98\x9b\x7d\xba\x11\ -\x1b\x13\xcd\x8d\xb3\x27\x73\xcf\x82\xab\x49\x4f\x4d\x0a\x4e\x27\ -\x52\x2c\x19\x9f\x27\x74\x89\xf1\xd7\x42\xc8\x05\x40\x08\xa1\xae\ -\xaf\x92\x77\xc6\x2a\xea\x0a\x10\x41\xd9\x91\x8a\x8f\x86\xf1\xb9\ -\x92\xf2\xda\xd0\xd6\x99\x37\x92\x82\xa2\xc1\xcc\x9e\x77\x1b\x99\ -\x39\x79\x46\x9b\xd2\x6b\x88\x8f\x8f\x65\xde\x8c\xcb\xb9\x7b\xc1\ -\x6c\x32\x52\x93\x83\xd8\x93\xdc\x6c\xb1\x2a\xf7\xe9\x19\xe5\xe7\ -\x2f\x21\x17\x00\x80\x29\x85\xc2\x5e\x7e\x4c\xde\x84\x54\x3f\x47\ -\x32\x38\x18\x7d\x14\xa6\x4a\x14\x05\x2a\x6a\x7a\xf7\x86\x60\x42\ -\x62\x32\x33\xe6\xde\xcc\x65\x63\x26\x60\x46\x46\xe9\x43\x51\x7e\ -\x36\xf3\xe7\x4e\x61\xde\xcc\x49\xc4\xc7\x05\x3d\xfd\xd4\xe1\x28\ -\xab\xe5\x86\x31\xd9\xc2\x90\x1a\xe5\x86\x08\x00\x40\x49\xae\x38\ -\x5e\x56\x2b\x6f\x14\xa8\xeb\x80\xb4\x60\xf4\x91\x9f\x2c\x49\x8e\ -\x91\x6c\xac\x52\x7a\x5d\xb5\x61\xc5\x62\xa1\x64\xd2\x34\xa6\xcc\ -\xb8\x96\xe8\x18\xb3\x86\x5a\x4f\x89\xb2\x5a\x98\x5a\x3a\x9a\xf9\ -\xd7\x5e\x49\xf1\xa8\x21\xa1\xda\x3b\x69\x52\x85\x72\xfd\x98\x6c\ -\x51\x1f\x8a\xce\x2e\x86\x61\x02\x00\x30\x21\x4f\xec\x2e\x3f\x26\ -\xe7\xa3\xaa\x1f\x11\xa4\xac\xc2\x49\x31\xbe\x12\x63\xfb\x8e\x0b\ -\x0e\x9c\x10\x78\x7b\xc1\x92\xa0\x70\xc0\x10\x66\xcf\xbb\x8d\x7e\ -\x59\xb9\x46\x9b\x12\xf1\x0c\x1f\x5c\xc8\xdc\x69\xa5\xcc\x99\x5a\ -\x4c\x5a\x4a\x90\xd6\xf7\x17\x43\xe0\x46\x55\xef\x28\xcd\xb7\x18\ -\x5a\xb4\xd0\x50\x01\x00\x28\xc9\x15\xab\xcb\x6a\x3c\xf7\x09\xc4\ -\xcb\x04\xe9\x78\xd0\x22\xe0\xb2\x2c\x49\x61\xaa\x64\x47\x9d\xa0\ -\xae\x3d\x32\xa7\xca\x49\xc9\xa9\x4c\x9f\x7b\x0b\x23\x46\x17\x1b\ -\x6d\x4a\xc4\xa2\x28\x82\xcb\x06\xf7\x67\xfa\xa4\x31\x4c\x9b\x3c\ -\x96\x82\x9c\x7e\x46\x98\xe1\x45\x95\x77\x95\x14\x44\x2d\x37\xa2\ -\xf3\xb3\x31\x5c\x00\x00\x26\xe4\x5b\x5f\xab\xa8\xf6\xc4\x4a\x21\ -\x9e\x25\x88\xee\xc9\x89\xd1\x30\xb9\x48\xd2\x64\x97\xec\xae\x17\ -\x1c\xef\x8c\x0c\x21\xb0\x58\x2c\x4c\xb8\x62\x26\x93\xa7\x5f\x43\ -\x74\xb4\x99\x12\x59\x2b\x09\xf1\x71\x94\x8e\x1d\xca\xa4\x71\x23\ -\x98\x52\x3a\x92\x7e\x69\x29\x46\x9a\x23\x85\x90\xff\x51\x9c\x6f\ -\x7d\xc3\x48\x23\x4e\x11\x16\x02\x00\x50\x5c\x60\x7d\xa1\xdc\xec\ -\x1a\x97\x00\x00\x09\x20\x49\x44\x41\x54\xac\xc6\x9b\x28\xe0\x6f\ -\xc1\xee\x2b\x3d\x0e\xae\x1c\x20\xe9\x70\x49\x2a\x9b\x05\x95\x2d\ -\x02\x87\xee\xa9\x16\xf4\xa1\xff\xe0\xe1\x5c\x3d\xef\x36\xd2\x32\ -\xb2\x8c\x36\x25\x62\x48\x49\x4e\x60\xd4\xd0\xfe\x8c\x1e\x36\x80\ -\xf1\xa3\x06\x33\x6a\xe8\x80\xc0\xd3\x70\xe9\x8c\x94\x3c\x52\x92\ -\x6f\x7d\xc6\x68\x3b\x4e\x11\x36\x02\x00\x30\x21\xdf\xf2\xf7\xb2\ -\x6a\x6f\xb4\x10\xfc\x31\x14\xfd\x25\x46\xc3\xc8\x6c\xc9\x65\x59\ -\x92\xda\x36\xc1\x8e\x7a\x81\x3d\x4c\x0a\x90\x26\x26\xa7\x30\x6d\ -\xf6\x8d\x8c\x1c\x3f\xd1\x68\x53\xc2\x1a\x45\x51\x28\xca\xcf\x62\ -\xf8\xa0\x02\xc6\x8d\x18\xc4\x98\x11\x03\x19\x50\x90\x1d\x9e\x0e\ -\x50\x82\x1f\x4f\xc8\xb7\x3c\x61\xb4\x19\x67\x13\x56\x02\x00\x30\ -\xa1\xc0\xf2\x44\x79\x8d\x37\x15\xf8\x79\xa8\xfa\x14\x02\xf2\x53\ -\x24\xd9\x89\x92\x6d\x75\xbe\x19\x81\x51\x28\x16\x0b\xe3\x4b\xa7\ -\x32\xf5\xea\x79\xe6\x74\xff\x2c\xa2\xac\x16\x32\x33\x52\x19\x50\ -\x90\xcd\xc0\xc2\x5c\x06\x16\xe6\x30\xb0\x20\x9b\xc2\xfc\x2c\x3d\ -\x83\x70\x82\x86\x84\x5f\x4c\xc8\xb3\x84\xdc\xd1\xa7\x3b\xc2\x50\ -\x26\x7d\x94\xd7\x78\x1f\x05\x0c\xf9\xc2\x2a\x5b\x04\x5b\x6a\x05\ -\xaa\x8e\x27\x06\x2d\xcd\x8d\x3c\xf3\xa7\x5f\x76\x79\xcd\xa0\xa1\ -\x23\x99\x79\xfd\xad\xa4\xa5\x1b\xb2\x31\x85\xcb\xe5\xa4\xb9\xf1\ -\x38\x4d\x27\xea\x69\x6f\x6d\xa6\xa3\xbd\x95\xb6\x96\x66\x3a\x3b\ -\xda\xb0\x77\x76\x60\xb7\xdb\x70\x3a\xf4\x8d\x51\x11\x42\x10\x1d\ -\x13\x47\x5c\x7c\x3c\x89\x49\xc9\x14\x64\xa5\x50\x94\x9d\x4c\x56\ -\x46\x2a\x19\xa9\xc9\x64\xf5\x4b\x21\x33\x3d\x95\x9c\xcc\x74\x14\ -\x25\x6c\x1f\xd7\xee\xf8\x7f\x25\xf9\x96\xae\xff\xf8\x06\x11\x76\ -\x33\x80\x53\x94\xe4\x5b\x1e\x2f\xaf\xf6\xb6\x22\xf8\x3b\x21\xc8\ -\x5b\x70\x36\x45\xa9\x92\x84\x28\xc9\x86\x2a\x05\x57\x08\x0a\x91\ -\xa6\xa4\x65\x30\xf3\xda\x05\x0c\x19\x11\x50\x99\x28\xdd\x88\x8e\ -\x8e\x21\x3b\xb7\xa0\xeb\xe0\x21\x29\x71\x38\xec\xd8\xed\x36\x5c\ -\x27\xc5\xc0\xe5\x74\xa2\xaa\x5e\xbc\xaa\x17\xb7\xeb\xdc\x72\xdd\ -\x42\x08\x62\xce\xf2\x53\x88\x89\x8d\x23\x26\x36\x96\xa8\xe8\x18\ -\xa2\x63\x62\xcf\x99\xe5\xc4\x58\x61\xce\x10\x15\x6b\xef\xc9\xf1\ -\x28\x85\xe0\xe1\xe2\x3c\x8b\xee\xd9\x7c\xf5\x22\x6c\x05\x00\xa0\ -\xa4\xc0\xf2\x54\x45\xad\xa7\x5d\x4a\xf1\x3c\x21\xb6\x35\x23\x01\ -\xa6\xf4\x57\x59\x73\x58\x09\x9a\xef\x80\x35\x2a\x8a\x92\x49\xd3\ -\xb9\x62\xfa\x5c\xa2\xa2\x23\x24\x9d\x91\x10\xc4\xc6\xc5\x13\x1b\ -\x17\xaf\x7b\xd3\x43\xfb\xc9\xde\x34\xf8\xbd\x12\xf9\xf5\x92\x3c\ -\xeb\x73\x46\x1b\xd2\x15\x61\x2d\x00\x00\xc5\x79\xd6\x97\xcb\x6a\ -\x3c\x1e\x81\x78\x11\x08\xe9\x28\x49\x8d\x83\xd1\xb9\x92\xad\xb5\ -\xfa\x4f\x3d\x07\x0f\x1b\xc5\xcc\xeb\x6f\x25\x35\x2d\xa8\xe9\x11\ -\x22\x8a\xcc\x84\x5e\xe0\xa5\xe5\xc3\x89\x94\x77\x4f\x28\x08\x8f\ -\xa3\xbe\xae\x08\x7b\x01\x80\x93\x7e\x02\x35\xb2\x5a\xa2\xbe\x05\ -\x84\x74\x81\x5c\x94\x22\xd9\x55\x27\x70\xeb\x14\x4f\x90\x9a\xde\ -\x8f\x59\xd7\x2d\x60\xd0\xb0\x51\xfa\x34\xd8\x8b\xe8\x0d\x5e\x9a\ -\x40\xb3\x50\xd5\x05\xc5\x85\x51\xab\x8c\x36\xc4\x1f\x22\x6a\x57\ -\xa5\xac\x4a\x0e\x11\x8a\xfa\x01\x30\x34\x94\xfd\x6e\xa8\x14\x1c\ -\xeb\xa1\xf7\x60\x7b\x5b\x0b\x3b\x2b\x36\x32\x71\xea\x2c\x2c\x96\ -\x88\xd0\xdd\x90\x73\x45\x91\x24\x3b\x29\xa2\x55\xe0\xb0\x2a\x94\ -\xeb\x4b\xf3\x84\xa1\xee\xbd\x5a\x88\x28\x01\x00\xd8\x5c\x2b\xfb\ -\x29\xd2\xfb\x16\x88\x2b\x43\xd5\xe7\x81\x46\xc1\x8e\xba\x88\xfb\ -\xaa\x22\x8e\xab\x87\xa8\x24\x86\xff\x89\xde\xc5\x11\x72\x63\x94\ -\xc5\x72\x93\x91\x81\x3d\x81\x10\x1e\xee\x51\x1a\x28\xcd\x13\x27\ -\x54\x61\xb9\x46\x20\xde\x0c\x55\x9f\xa9\x66\xb0\x5d\xd0\xc9\x49\ -\x92\x91\x3b\xf8\xa5\x58\xe2\xf0\x5a\x66\x44\xda\xe0\x87\x08\x14\ -\x00\x80\xd2\x3c\x61\x1b\x9f\x27\x6e\x05\x7e\x04\x04\xfd\xa0\x2e\ -\x39\x36\xa2\xa7\xa5\x61\x8f\x10\x11\x5b\xdd\x49\x02\x8f\x17\xe7\ -\x8b\x3b\xa6\x14\x86\x26\x89\xa7\xde\x44\xa4\x00\x00\x08\x21\x64\ -\x49\xbe\xe5\x71\x45\xaa\x73\x80\x86\x60\xf6\x15\x6d\x81\x58\x73\ -\xd9\x1e\x34\x06\xa6\x49\x92\x23\x6f\x96\xd5\x28\x85\x7a\x5d\x49\ -\xbe\xe5\x47\x42\x88\x88\x4d\x39\x13\xb1\x02\x70\x8a\xf1\x05\x51\ -\x2b\x3d\x28\xa5\x48\xb9\x29\x98\xfd\x24\xc5\x44\xe4\x1b\x2a\xec\ -\x51\xf0\x9d\xff\x47\x12\x12\x2a\x2c\x16\x65\xe2\x84\xbc\xa8\x8f\ -\x8c\xb6\xa5\xa7\x44\xbc\x00\x00\x5c\x9e\x2f\xaa\x1c\xd2\x32\x5d\ -\x40\xd0\x9c\x2e\xe2\xa2\x82\xd5\x72\xdf\x26\x3f\x55\x46\xd4\x77\ -\x2b\xe1\x5f\x6d\x2e\x65\xca\xb8\x1c\x11\xb2\xd4\xdd\xc1\xa4\xd7\ -\x6d\x6d\x97\xd7\x7a\x6e\x45\x8a\x7f\x02\x3d\x2f\xce\x76\x16\x3b\ -\xeb\x05\xfb\x4f\xf4\xba\xaf\xcb\x70\xa6\x0f\x52\x49\x8b\x84\x04\ -\xc6\x82\x56\xa4\xfc\x66\x49\xbe\xf5\xdf\x46\x9b\xa2\x27\xbd\x62\ -\x06\x70\x36\x25\x79\xd6\x25\x1e\x94\xf1\x08\xb9\xca\x68\x5b\x4c\ -\xba\x26\x3d\x9e\x88\x18\xfc\x52\x88\x95\x16\x55\x19\xdd\xdb\x06\ -\x3f\xf4\x42\x01\x00\xdf\x92\xa0\x38\xd7\x32\x4b\x08\x7e\x00\xb8\ -\xba\xbd\xc1\x24\xe4\x08\x60\x54\x56\xd8\xef\x9d\x79\x24\xfc\xa2\ -\x24\x57\x5c\x3d\xae\x40\x54\x1b\x6d\x4c\x30\xe8\x95\x02\x00\xbe\ -\x53\x82\xe2\x3c\xcb\x9f\x15\x94\xc9\x40\x79\x4f\xdb\xeb\x2b\xf5\ -\x05\x42\xc5\x88\x2c\x49\x46\x58\x97\x2a\x94\x9b\x91\x4a\xe9\x84\ -\x7c\xcb\x63\x91\xbc\xcb\xdf\x1d\xbd\x56\x00\x4e\x31\x3e\x5f\x54\ -\xb4\xe6\x29\x93\x4e\xce\x06\x02\x2e\x18\x66\x8e\xff\x9e\x23\x84\ -\x6f\xca\x3f\xa9\x48\x32\x3c\x33\x6c\xbf\x51\x3b\xf0\xa3\x03\x79\ -\x96\xc9\x25\x05\x62\xab\xd1\xc6\x04\x9b\x3e\xb5\xab\xb5\xa9\x5e\ -\x0e\xb2\x78\xe4\x53\x20\xe7\x68\xbd\x77\xdb\x31\xc1\xa1\xa6\x3e\ -\xf5\x75\x05\x8c\x00\x32\x12\x24\xfd\x12\x20\x21\x0a\x62\xa3\x20\ -\xc6\x22\x89\x8f\x06\x6b\x38\xbf\x72\xa4\x5c\xad\x60\x79\x68\x7c\ -\x81\xd8\x67\xb4\x29\xa1\xa2\xcf\x3d\xd1\x52\x4a\x51\x5e\xeb\xbd\ -\x5f\x20\x7e\x03\x64\xfb\x7b\xdf\xf6\x3a\xc1\xc1\xc6\x3e\xf7\x75\ -\x69\x26\x3b\x49\x32\x32\x4b\x92\x12\x59\x8e\x3d\x75\x48\xf9\x68\ -\x49\x81\xf5\x45\xa3\x0d\x09\x35\x7d\xf6\x89\xde\x5a\x27\x13\x3c\ -\x5e\xf5\xbf\x04\x3c\x0a\x74\xfb\xb8\x9a\x01\x41\x5d\x93\x99\x20\ -\x19\x96\x19\x61\x31\xfd\x02\x37\x92\x27\x5d\xb1\xca\xcf\x26\x67\ -\x88\x36\xa3\xcd\x31\x82\x3e\xff\x44\x97\x55\xc9\x21\xc2\x22\x7f\ -\x8d\x94\xb7\x77\x75\x5d\x5d\xbb\xe0\x8b\xca\x3e\xff\x75\x5d\x40\ -\x46\x02\x8c\xce\x8e\x90\xb3\xfc\xb3\x10\x88\xf7\xbc\x5e\xf1\x83\ -\xd2\x22\x71\xd0\x68\x5b\x8c\xc4\x7c\xa2\x4f\x52\x5e\xe3\xbe\x1a\ -\x94\xc7\x81\x92\x8b\xfd\xbc\xcd\x01\x2b\x0e\x86\xf3\x02\x36\xb4\ -\xc4\x45\xc1\x98\x1c\x49\x5e\x72\x04\xbd\xf1\x01\x90\x9b\xa5\x94\ -\x8f\x4e\x28\x88\x5a\x61\xb4\x25\xe1\x80\x29\x00\xe7\x51\x5e\xe3\ -\xbe\x5a\xa2\xfc\x4e\xc0\x39\xf5\xb7\xdc\x5e\x78\x7f\x8f\x29\x00\ -\xe0\x9b\xe6\x4f\x2c\x94\x44\x47\x56\xfe\xbe\x9d\x42\xca\x5f\x8c\ -\xcf\xb7\xbc\x61\x44\x19\xee\x70\xc5\x7c\xa2\xcf\xa3\x24\x3f\xea\ -\xd3\x92\x3c\x65\x82\x44\xde\x04\x6c\x3b\xf5\xb9\xd5\x02\x61\x52\ -\x5c\xc6\x50\xd2\xe2\x60\x72\xff\x88\x1a\xfc\xbb\x91\xf2\xbe\x03\ -\x79\xca\xb8\xe2\x02\xeb\xeb\xe6\xe0\x3f\x17\x73\x06\xd0\x05\x8b\ -\xa5\xb4\x0c\x3e\xe6\xbd\x53\x48\xf1\x5f\x2d\x76\xc6\xae\x3a\xd4\ -\xb7\x15\x40\x00\x33\x07\xab\x91\x12\xba\xbb\x45\x08\xf9\xfb\xf1\ -\xb9\x96\x57\x7b\xb3\x23\x4f\x4f\x31\x05\xc0\x4f\xde\xde\x2d\x17\ -\x1c\x3a\xc1\x53\x2d\x76\xc2\xd7\x85\x25\xc8\xa4\xc4\xc1\xcc\x41\ -\xe1\x3e\x96\xe4\x3a\x09\x8f\x97\xe4\x59\xde\x33\xdf\xf6\xdd\x63\ -\x0a\x80\x46\x16\x95\xcb\x9b\x3a\x9d\x3c\xde\xd0\xc9\x08\x4f\xb8\ -\x8f\x05\x9d\xc9\x4f\xf6\xad\xfd\xc3\x10\x17\x82\x7f\xab\x5e\xe5\ -\x0f\xa5\x85\x62\xbb\xd1\xc6\x44\x12\xa6\x00\x04\xc8\xe2\xcd\xb2\ -\xa8\xd9\xa3\xfe\xae\xd9\xf6\xff\xb7\x77\x37\x3d\x4d\x04\x61\x1c\ -\xc0\xff\xcf\x4c\x09\x50\x8a\x21\x82\xd8\x42\x40\x88\xc1\x10\x09\ -\x28\x95\x78\x20\x26\x86\x53\x23\x07\x8d\x9c\xf5\x13\x78\xf4\xa4\ -\x37\x3f\x84\x31\x72\xd0\x18\xbd\x10\x3d\x18\xe2\x81\x83\x06\x0f\ -\x36\xbe\x04\x4d\x14\xc3\x41\x13\x4b\xe2\xa1\x18\x15\xb5\x94\x76\ -\xdf\xe6\x79\x3c\xd4\x78\x52\x23\xd2\x76\xd9\x3a\xbf\x0f\xb0\xf3\ -\xec\x66\xe7\x9f\xd9\xdd\xd9\x19\x3a\x5d\x0a\x28\x22\xbb\x7a\x6c\ -\x4f\x77\x42\x30\xb9\x6f\x47\x05\xc0\x1b\x00\xd7\xb4\x56\xd7\x0f\ -\x25\xa9\xa6\xab\x42\x35\x2a\x1b\x00\x55\x70\xe3\xb9\x9c\xd9\xf4\ -\x70\xf1\x73\x09\xc3\xbe\x69\xdc\x6b\xda\xda\x04\x64\x0e\x84\x3e\ -\xec\x71\x41\x34\x0f\x31\xb3\xe3\x3d\xb1\x07\x76\x98\xbf\x3d\x0d\ -\x7b\xb3\x86\x61\x31\x27\xc9\xf7\xeb\x7c\x69\xbd\xac\x66\xbe\x94\ -\xd1\xd5\x88\x7f\x10\x4e\x0f\x73\x18\x5f\x00\x0c\x40\x8b\x44\x3c\ -\x07\x57\xdf\x19\x1f\xa4\xaf\x75\xaf\xa0\x41\xd9\x00\xa8\x91\xd9\ -\x67\x32\xc2\x06\x17\x4a\x01\x32\xdf\x9c\xc6\x09\x83\x63\x03\x82\ -\xae\xfa\x4c\xf7\x65\x40\x1e\x13\xd1\x6d\x3f\xa6\xe6\x8e\x76\xd3\ -\x5a\x3d\x1a\xfd\xdf\xd8\x00\xa8\x83\xbb\x2f\xe4\xe0\xba\xe0\xfc\ -\x86\x83\x4c\xc1\x41\x4f\x94\x1f\x13\xd2\xbd\x82\xfe\x8e\x9a\x05\ -\x40\x01\x42\xf7\x49\xf1\x82\xf1\xf4\xbd\x89\x01\xca\xd7\xaa\x21\ -\xab\x22\xb2\x37\x62\x54\x5d\x5e\x94\x44\xbc\x9d\xcf\x39\xbe\x9a\ -\x29\x7a\x32\x5a\xf4\xa8\x35\x4a\xa3\x83\x23\xbd\x82\xbe\xea\x06\ -\xc0\x4b\x00\x0b\xc4\xbc\x60\x3e\xc4\xb2\x13\x13\xe4\x57\xf3\xe0\ -\xd6\x9f\xd9\x00\x08\xd9\xcd\x15\x49\x99\x32\x9f\x2d\x79\x74\xb2\ -\xec\xd3\xc8\x86\x83\x8e\xd0\x5f\xb3\xfd\x46\x73\xac\x32\x11\x68\ -\x1b\x7b\x24\x04\xa8\x74\xf8\x2c\x89\x3c\x12\xad\x1f\xa6\x53\xf4\ -\xb1\x6a\x05\x5a\x5b\x66\x03\x60\x87\xb9\xf5\x44\x76\xe9\x16\x9c\ -\x72\x03\x3e\xe1\x06\xea\xf0\xa6\x8f\xbe\x82\x83\x04\x87\x3c\x4a\ -\x20\x54\xa6\x00\xef\x4d\xfc\x75\x21\x0e\x80\x15\x01\x5e\x91\x60\ -\x99\x48\x3d\x4d\x94\xb0\x34\x34\x44\x6e\xed\xaa\xb4\xb6\xca\x06\ -\x40\x04\x5c\x5d\x92\xb8\x12\x4c\x13\xf3\x71\x03\x1a\xf3\x03\xf4\ -\xbb\x86\x3a\xcb\x3e\xda\x3c\x53\x9f\xff\x39\xf6\x77\x0a\x46\x93\ -\xbf\xe8\xfc\x84\x3c\x44\x72\x02\xca\x11\xe1\x9d\xb0\xbc\x86\xd2\ -\xcb\x85\x14\xde\x4e\x11\x05\xf5\xa8\xcd\xfa\x77\x36\x00\x22\xee\ -\x4a\x56\xba\x5b\x9a\x31\x49\xe0\xb1\x00\xaa\x4f\x8c\x24\x0d\x63\ -\x8f\x2f\xd8\x6d\x98\xda\x03\x96\x36\x16\x34\x31\x93\x06\x80\x80\ -\xa1\x01\x90\x61\x90\xf9\xd1\x9f\x35\x01\x5a\xfd\x5c\xf6\x90\x63\ -\x0a\xac\x35\x02\x05\xf1\x34\xa1\xac\x15\x8a\xe9\x5e\x99\x8f\x37\ -\x21\x0f\x91\x4f\xac\x64\x4d\x10\x5b\xdd\x70\xb1\x3a\x35\x48\x4e\ -\x38\x67\x6e\x55\xc3\x77\x15\x65\xab\x07\x70\xac\xd6\x54\x00\x00\ -\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x05\xc2\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ -\x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ -\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ -\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x05\x3f\x49\x44\ -\x41\x54\x48\x89\x7d\x95\x69\x6c\x54\x55\x14\x80\xbf\xfb\xe6\x31\ -\xe5\xcd\x74\x1e\xb5\x53\x5a\x9c\x96\xca\x5a\x01\x31\x85\x52\x11\ -\xab\x60\x52\x40\xc3\x22\x1a\x14\x89\x10\x23\xb8\x41\x5c\xb0\x44\ -\x82\x06\x7f\x68\xa2\xd1\xa8\x08\x08\x46\x04\x11\x90\x62\xaa\x56\ -\x45\x5a\x4b\x54\x44\x04\x62\x44\x96\x41\x0d\x4b\x2d\x52\x5a\xa0\ -\x0b\xd0\x5a\x3a\x9d\x4e\x3b\xed\xbc\x77\xfc\x51\x66\xa4\x1b\xe7\ -\xd7\xbb\x27\xe7\x7e\xe7\x9d\xf5\x2a\xae\x23\xf9\xc7\x24\xa7\x35\ -\x2c\x2f\x36\xb7\x33\xa9\xd5\x52\x66\xb8\x5d\x9c\x22\x8a\x9c\xc1\ -\x52\x99\xe4\xa2\x54\x90\xef\x2c\xb4\x9d\xe3\x53\xd5\xf9\xde\x18\ -\xaa\x27\xe5\xe6\x23\x72\x7b\x30\x2c\xf9\xf5\x21\x35\x5c\xa4\x43\ -\xe7\x33\x85\x54\x13\xaa\x02\x30\x34\x51\xf0\xba\x63\xe6\x36\x4a\ -\x7d\xad\x94\x5a\x3e\xf6\x46\x55\xd1\x95\xa5\x75\x55\x7c\x7d\xdc\ -\xca\x13\x5b\x0e\x04\xc3\xff\xc3\x5d\x7d\xc0\xa1\x41\xa2\x4b\xc8\ -\x4e\x13\x3c\x7d\xff\xb7\x17\x11\x0d\x91\x39\xb6\x6d\xff\xe5\xaf\ -\x8a\xcc\xba\x6e\x04\x1b\x0e\xd9\x3b\x83\xad\xcc\x6a\x0c\x34\x71\ -\xba\xac\x8c\xfa\xfa\x3a\x0c\x09\xa2\x22\x21\x3c\xf1\x06\x8b\xe6\ -\xcd\xc4\x65\xc4\x01\x50\x71\xbe\x96\x55\x9f\x7c\xc3\x1b\xcb\x16\ -\x60\xc6\xbb\xae\x89\x86\xa5\x59\x3e\xc7\xda\xa8\x42\x8f\x7e\x6c\ -\xfa\x5d\x56\x9d\xa8\x6c\x98\xb5\xef\xc7\x9d\x94\x9d\xfc\x13\xdb\ -\xb6\xbb\xa5\xee\xb1\xd9\x53\x71\x3a\x75\x36\x7c\x56\x42\x61\xc9\ -\x7e\x22\x96\xd5\x3d\x23\xc2\xea\x92\x93\x91\xd0\x8c\x51\xfa\xa6\ -\x58\x8a\xb6\x1f\x95\x09\x87\x4f\x55\xe4\x6d\xfd\xf0\x1d\x4a\x8f\ -\x1f\xeb\x11\x1e\x95\x40\xa0\x99\x82\xa2\xbd\x3d\xc1\x63\x4e\x4c\ -\x43\xad\xd9\xf6\xbb\x64\xc7\x1c\x5c\xb8\xdc\x54\xb0\xe3\xb3\x4f\ -\x54\xb8\x35\x04\x40\x3f\xd3\xcd\x73\x8f\xdd\xcf\xca\x15\x4f\xf7\ -\xea\x28\x2a\xaa\x4b\x9b\xd8\x02\xfb\xca\x35\x77\x63\x84\x02\x00\ -\xfd\x48\xb5\xa4\x7f\x5a\xb8\x3b\x3d\x18\x0c\x00\x60\x9a\x1e\x3e\ -\x7e\xeb\x05\x7c\x29\x5e\x02\xc1\x50\xaf\xe0\x74\x5f\x32\x79\x4f\ -\xcc\xc6\xe3\x76\x11\x08\x86\xb8\x12\x08\xe2\xf1\xc4\x73\xfa\x8a\ -\x9b\x36\x0b\xea\x42\x0c\x2b\xf0\x4b\xb6\xae\x61\x3f\x70\xe0\xd0\ -\x1f\xb1\x6e\x7a\x6a\xee\xbd\xf8\x52\xbc\xbd\x82\x75\xdd\xc1\xe2\ -\xf9\x33\x79\x68\xfa\x44\x76\xfd\x72\x98\xf7\xb7\xec\xa0\xf2\xc2\ -\xc5\xab\xd1\x28\xd2\x87\x64\x90\x3b\x6d\x36\xde\xfe\x03\x08\xb4\ -\xc9\x2b\x3a\xa2\xa6\x55\xd5\xfe\x1b\x03\x4c\x1c\x3f\xba\x47\xf0\ -\x84\xac\x91\xb8\xdd\x06\x71\xce\x3e\x8c\xbe\x79\x10\x8b\x56\xbc\ -\xcf\x99\xca\xea\x4e\x36\x22\x42\xe5\x99\xbf\xf9\x62\xeb\x07\x2c\ -\x78\xe6\x25\x5a\x5c\x9e\x1c\xfd\xd7\x72\xfb\xae\xe6\x50\x47\x2a\ -\x34\x4d\x91\xd8\xcf\xec\x74\x29\x35\x25\x89\x25\x0b\x1f\xe0\xce\ -\xec\x5b\xa8\x6b\x68\xe4\xed\x8f\x8a\xd9\x7d\xc0\x8f\x5c\x1d\x92\ -\x21\xe9\x3e\x32\x47\x0e\xa6\xb6\xae\x81\x83\xfe\x53\x88\x08\xa1\ -\x60\x13\x47\x7f\xdb\x47\xe2\xb4\x99\x37\xe8\x83\x93\x94\xee\xd0\ -\x1d\x58\x11\x0b\xdb\x16\x82\xcd\x21\x4c\x4f\xc7\x98\xba\x8d\xbe\ -\xe4\xaf\x59\x8e\xa6\x69\x14\x14\xed\x65\x73\xe1\x0f\xb4\xb4\x84\ -\x01\xb8\x29\x2d\x85\xbc\x85\xb3\xb9\x2d\x33\x03\x80\xcb\xcd\x8a\ -\xd5\xf9\x7b\xd8\xff\x53\x31\x00\xe5\x65\x27\x69\xbf\x67\x46\x1f\ -\xdd\x67\x12\x49\xf7\x25\x73\xf6\x5c\x0d\x00\x3b\x76\x1f\x64\xec\ -\x1d\x53\x68\x0a\x43\xff\x78\x07\xa5\xa5\xff\xf0\x79\xe1\x37\x9c\ -\xab\xea\xc8\xb3\x61\xc4\xf1\xf8\x9c\x7b\x99\x33\x63\x12\xb6\x6d\ -\x63\x59\x36\x0e\x87\x86\xd7\x2d\xe4\x4e\x9a\x10\x73\xd0\x50\x7f\ -\x09\xa4\x63\xd0\xaa\x73\xb2\x46\x65\x44\x1d\x6c\x2a\x28\x61\xf0\ -\xe1\x72\xdc\x66\x3f\xaa\xcf\x9d\xa5\xfe\x72\x6d\xac\x80\x53\x26\ -\x66\xf1\xec\xa3\xf7\x91\x74\x43\x3f\x7e\x3d\x72\x82\xb5\x5b\xbe\ -\xe5\xe3\xb7\x97\x62\xc6\xbb\xd0\x80\x9c\x61\x06\x4a\x53\x88\x2d\ -\x44\x22\xed\xc4\xe9\x12\xd6\x41\x95\xcf\x9d\x79\x77\xc6\xae\xbd\ -\x87\x68\x68\x6c\x42\x44\x28\x3f\x7d\xb2\x53\x1d\xfa\x0f\xf0\x91\ -\x3b\xfd\x21\x46\x8f\x18\x4a\xbc\x2b\xcc\xb2\x37\x37\x72\xd0\x7f\ -\xaa\x5b\x23\x04\x9b\x1a\x11\xbb\xa3\x36\xa6\xc7\xcd\xd4\xe1\xea\ -\x67\x4d\x90\x92\xc4\x04\x0f\xeb\x5e\x7b\x86\x31\xa3\x47\x74\x9a\ -\x9c\xa4\x14\x1f\x53\x66\xcc\x61\xde\x93\x4b\xa9\x39\x77\x96\x8b\ -\xf5\xcd\xfc\x75\xbe\xb5\x47\x38\xc0\x81\x43\xc7\xaf\x69\x0e\x2f\ -\x0a\x29\xd6\x2d\xb4\x9d\x3a\xf6\x9a\x41\x03\x07\x38\x96\x3e\xbf\ -\x98\xa3\x15\xad\x84\x5a\x42\xb8\x0c\x17\xce\xbe\x06\x15\x67\x4a\ -\xd9\xb6\xfe\x5d\x1a\xea\x2f\x71\xcb\x98\xf1\x9c\xae\xeb\x71\xc3\ -\x53\x75\xb1\x8e\xcd\x85\xdf\xc7\xce\xb9\x77\x64\xda\x0e\xb4\x62\ -\x7d\x7c\xaa\x3a\xef\xaf\xb2\xb6\x02\x4f\x24\x18\x42\x9c\x61\xe0\ -\xec\x6b\x00\x10\x6e\x0d\xf1\xd5\xb6\xf5\x3d\x02\xa3\xb2\xc7\x5f\ -\x49\xcb\xbf\x35\x6c\xff\x76\x4f\x6c\xf2\x93\xbd\x09\x4c\xce\xc9\ -\xda\x96\x99\xa6\x2e\xe8\x00\x9a\xad\xbd\x6a\x69\xf6\xc3\x5e\x17\ -\x9e\x21\x89\xc2\x99\xfa\x8e\xbf\x8c\xbe\x07\xd7\x93\xf7\xd6\x6e\ -\xec\x74\x36\x3d\x6e\x5e\x7f\x71\x41\x73\x52\x42\xe2\x0a\xb8\xba\ -\xec\xc6\x0c\x54\x55\x4a\x93\xb9\x80\x35\xb2\xbf\x10\xa7\x77\x86\ -\x68\x0e\x07\xd9\x77\xe6\x62\xb8\xdc\xf4\x26\xba\xc3\xc1\xd4\x89\ -\x59\x6c\x5d\xb9\xcc\x1e\x95\x91\xfe\x48\xf6\x20\x55\x03\x5d\x1e\ -\x1c\x7f\xb5\xb5\x04\x61\x75\x9b\x85\x76\xbc\x56\x51\x56\xd3\x42\ -\xd1\x17\x5b\x98\x3c\xfd\x41\xbc\xc9\x03\x00\x68\x6f\x0b\xb3\xff\ -\xa7\x62\x5a\x5b\x5a\x18\xe8\x35\x18\x98\xec\x26\xdd\x97\xc2\xb8\ -\x5b\x87\x91\x60\xc6\xdb\x40\x5e\x56\xaa\x63\x5d\x94\xd9\xad\x62\ -\xfe\xaa\xc8\x2c\x41\x6d\x6f\xb7\xf0\xec\x3a\xa5\xba\xef\xe3\x68\ -\x54\x0a\x26\x0f\xb3\x71\x3b\x63\xaa\x80\x88\xcc\x1f\x97\xa6\x7f\ -\xd7\xc9\xae\xeb\xc5\xac\x54\xbd\x48\x69\xda\x50\x5d\xb1\x4e\xeb\ -\x05\x3e\xd4\x2b\x4c\x1d\x1e\x83\xdb\x08\xf9\x76\xbb\x36\xa2\x2b\ -\xbc\xc7\x08\xae\x95\x2f\x4f\xc8\x98\x40\xb3\xfd\x72\x30\xac\x72\ -\xdb\x2c\x65\x86\x2d\x9c\x11\x1b\x75\xcf\x70\xfb\x1f\xc3\xa9\xca\ -\x94\x48\x89\x26\x5a\x51\x66\x9a\xba\xd0\x1b\xe3\x3f\x12\xae\x2a\ -\x58\xfd\xf2\x8c\xf2\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -" - -qt_resource_name = b"\ -\x00\x05\ -\x00\x6f\xa6\x53\ -\x00\x69\ -\x00\x63\x00\x6f\x00\x6e\x00\x73\ -\x00\x17\ -\x0b\xce\x1a\xe7\ -\x00\x71\ -\x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x31\x00\x32\x00\x38\x00\x78\x00\x31\ -\x00\x32\x00\x38\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x07\xc8\x36\xe7\ -\x00\x71\ -\x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x35\x00\x31\x00\x32\x00\x78\x00\x35\ -\x00\x31\x00\x32\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x15\ -\x02\x0a\xc7\x87\ -\x00\x71\ -\x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x33\x00\x32\x00\x78\x00\x33\x00\x32\ -\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x15\ -\x02\xf8\xd9\xc7\ -\x00\x71\ -\x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x36\x00\x34\x00\x78\x00\x36\x00\x34\ -\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x15\ -\x02\x26\xc3\x07\ -\x00\x71\ -\x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x31\x00\x36\x00\x78\x00\x31\x00\x36\ -\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x15\ -\x02\x14\xc5\x47\ -\x00\x71\ -\x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x34\x00\x38\x00\x78\x00\x34\x00\x38\ -\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x15\ -\x02\xa6\xd3\x07\ -\x00\x71\ -\x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x39\x00\x36\x00\x78\x00\x39\x00\x36\ -\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x0a\x1f\xbe\xe7\ -\x00\x71\ -\x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x32\x00\x35\x00\x36\x00\x78\x00\x32\ -\x00\x35\x00\x36\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x15\ -\x02\x38\xc1\xc7\ -\x00\x71\ -\x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x32\x00\x34\x00\x78\x00\x32\x00\x34\ -\x00\x2e\x00\x70\x00\x6e\x00\x67\ -" - -qt_resource_struct = b"\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x02\ -\x00\x00\x00\x78\x00\x00\x00\x00\x00\x01\x00\x00\xbd\xb9\ -\x00\x00\x01\x08\x00\x00\x00\x00\x00\x01\x00\x00\xda\x06\ -\x00\x00\x00\xd8\x00\x00\x00\x00\x00\x01\x00\x00\xd6\x51\ -\x00\x00\x01\x9c\x00\x00\x00\x00\x00\x01\x00\x01\x49\xa3\ -\x00\x00\x01\x38\x00\x00\x00\x00\x00\x01\x00\x00\xe6\x0b\ -\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00\xc5\x97\ -\x00\x00\x00\x44\x00\x00\x00\x00\x00\x01\x00\x00\x23\x02\ -\x00\x00\x01\x68\x00\x00\x00\x00\x00\x01\x00\x00\xff\xe4\ -\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -" - -def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 7d069909a..0fa74f4e7 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -81,7 +81,7 @@ def log_signals(obj: QObject) -> QObject: pass if inspect.isclass(obj): - old_init = obj.__init__ # type: ignore[misc] + old_init = obj.__init__ @functools.wraps(old_init) def new_init(self: Any, *args: Any, **kwargs: Any) -> None: @@ -89,7 +89,7 @@ def log_signals(obj: QObject) -> QObject: old_init(self, *args, **kwargs) connect_log_slot(self) - obj.__init__ = new_init # type: ignore[misc] + obj.__init__ = new_init else: connect_log_slot(obj) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 9cd07e2e3..22035e074 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -44,6 +44,7 @@ except ImportError: if TYPE_CHECKING: from qutebrowser.config import config as configmodule + from typing import TextIO _log_inited = False _args = None @@ -245,7 +246,9 @@ def disable_qt_msghandler() -> Iterator[None]: @contextlib.contextmanager def py_warning_filter(action: str = 'ignore', **kwargs: Any) -> Iterator[None]: """Contextmanager to temporarily disable certain Python warnings.""" - warnings.filterwarnings(action, **kwargs) + # FIXME Use Literal['default', 'error', 'ignore', 'always', 'module', 'once'] + # once we use Python 3.8 or typing_extensions + warnings.filterwarnings(action, **kwargs) # type: ignore[arg-type] yield if _log_inited: _init_py_warnings() @@ -257,7 +260,7 @@ def _init_handlers( force_color: bool, json_logging: bool, ram_capacity: int -) -> Tuple[logging.StreamHandler, Optional['RAMHandler']]: +) -> Tuple["logging.StreamHandler[TextIO]", Optional['RAMHandler']]: """Init log handlers. Args: @@ -381,8 +384,8 @@ def qt_message_handler(msg_type: QtCore.QtMsgType, """Qt message handler to redirect qWarning etc. to the logging system. Args: - QtMsgType msg_type: The level of the message. - QMessageLogContext context: The source code location of the message. + msg_type: The level of the message. + context: The source code location of the message. msg: The message text. """ # Mapping from Qt logging levels to the matching logging module levels. @@ -727,7 +730,8 @@ class ColoredFormatter(logging.Formatter): datefmt: str, style: str, *, use_colors: bool) -> None: - super().__init__(fmt, datefmt, style) + # FIXME Use Literal["%", "{", "$"] once we use Python 3.8 or typing_extensions + super().__init__(fmt, datefmt, style) # type: ignore[arg-type] self.use_colors = use_colors def format(self, record: logging.LogRecord) -> str: diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index 50a438637..c490aa4e8 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -18,7 +18,8 @@ # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. # Because every method needs to have a log_stack argument -# pylint: disable=unused-argument +# and because we use *args a lot +# pylint: disable=unused-argument,differing-param-doc """Message singleton so we don't have to define unneeded signals.""" @@ -125,7 +126,7 @@ def ask(*args: Any, **kwargs: Any) -> Any: Return: The answer the user gave or None if the prompt was cancelled. """ - question = _build_question(*args, **kwargs) # pylint: disable=missing-kwoa + question = _build_question(*args, **kwargs) global_bridge.ask(question, blocking=True) answer = question.answer question.deleteLater() @@ -139,7 +140,7 @@ def ask_async(title: str, """Ask an async question in the statusbar. Args: - message: The message to display to the user. + title: The message to display to the user. mode: A PromptMode. handler: The function to get called with the answer as argument. default: The default value to display. @@ -174,7 +175,7 @@ def confirm_async(*, yes_action: _ActionType, The question object. """ kwargs['mode'] = usertypes.PromptMode.yesno - question = _build_question(**kwargs) # pylint: disable=missing-kwoa + question = _build_question(**kwargs) question.answered_yes.connect(yes_action) if no_action is not None: question.answered_no.connect(no_action) diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index 0819a5d0a..c3cdb1071 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -170,7 +170,7 @@ def _get_tab_registry(win_id: _WindowTab, window: Optional[QWidget] = QApplication.activeWindow() if window is None or not hasattr(window, 'win_id'): raise RegistryUnavailableError('tab') - win_id = window.win_id + win_id = window.win_id # type: ignore[attr-defined] elif win_id is None: raise TypeError("window is None with scope tab!") @@ -205,7 +205,7 @@ def _get_window_registry(window: _WindowTab) -> ObjectRegistry: raise RegistryUnavailableError('window') try: - return win.registry + return win.registry # type: ignore[attr-defined] except AttributeError: raise RegistryUnavailableError('window') diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index ff5ec9d9a..b14b0faf4 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -24,19 +24,25 @@ import sys import contextlib import posixpath import pathlib -from typing import Iterator, Iterable +from typing import Iterator, Iterable, Union # We cannot use the stdlib version on 3.7-3.8 because we need the files() API. if sys.version_info >= (3, 9): import importlib.resources as importlib_resources + from importlib.abc import Traversable else: # pragma: no cover import importlib_resources + from importlib_resources.abc import Traversable import qutebrowser _cache = {} -def _path(filename: str) -> pathlib.Path: + +_ResourceType = Union[Traversable, pathlib.Path] + + +def _path(filename: str) -> _ResourceType: """Get a pathlib.Path object for a resource.""" assert not posixpath.isabs(filename), filename assert os.path.pardir not in filename.split(posixpath.sep), filename @@ -64,7 +70,7 @@ def _keyerror_workaround() -> Iterator[None]: def _glob( - resource_path: pathlib.Path, + resource_path: _ResourceType, subdir: str, ext: str, ) -> Iterable[str]: @@ -77,14 +83,11 @@ def _glob( glob_path = resource_path / subdir if isinstance(resource_path, pathlib.Path): + assert isinstance(glob_path, pathlib.Path) for full_path in glob_path.glob(f'*{ext}'): # . is contained in ext yield full_path.relative_to(resource_path).as_posix() - else: # zipfile.Path or importlib_resources compat object - # Unfortunately, we can't tell mypy about resource_path being of type - # Union[pathlib.Path, zipfile.Path] because we set "python_version = 3.6" in - # .mypy.ini, but the zipfiel stubs (correctly) only declare zipfile.Path with - # Python 3.8... - assert glob_path.is_dir(), glob_path # type: ignore[unreachable] + else: # zipfile.Path or other importlib_resources.abc.Traversable + assert glob_path.is_dir(), glob_path for subpath in glob_path.iterdir(): if subpath.name.endswith(ext): yield posixpath.join(subdir, subpath.name) diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index f1bd01a5f..c753e9de6 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -147,7 +147,8 @@ def _init_data(args: Optional[argparse.Namespace]) -> None: # system_data _locations.pop(_Location.system_data, None) # Remove old state if utils.is_linux: - path = '/usr/share/' + APPNAME + prefix = '/app' if version.is_flatpak() else '/usr' + path = f'{prefix}/share/{APPNAME}' if os.path.exists(path): _locations[_Location.system_data] = path diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 002f10411..cfba2c1d8 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -95,6 +95,7 @@ def _parse_search_term(s: str) -> Tuple[Optional[str], Optional[str]]: engine = None term = s else: + # pylint: disable=else-if-used if config.val.url.open_base_url and s in config.val.url.searchengines: engine = s term = None @@ -328,6 +329,7 @@ def invalid_url_error(url: QUrl, action: str) -> None: """Display an error message for a URL. Args: + url: The URL to display a message for. action: The action which was interrupted by the error. """ if url.isValid(): diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index ee0f899cc..56c29899d 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -301,6 +301,7 @@ class Backend(enum.Enum): """The backend being used (usertypes.backend).""" + # pylint: disable=invalid-name QtWebKit = enum.auto() QtWebEngine = enum.auto() diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index a56769255..77543f161 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -44,7 +44,7 @@ except ImportError: # pragma: no cover """Empty stub at runtime.""" -from PyQt5.QtCore import QUrl, QVersionNumber, QRect +from PyQt5.QtCore import QUrl, QVersionNumber, QRect, QPoint from PyQt5.QtGui import QClipboard, QDesktopServices from PyQt5.QtWidgets import QApplication @@ -382,7 +382,7 @@ def get_repr(obj: Any, constructor: bool = False, **attrs: Any) -> str: obj: The object to get a repr for. constructor: If True, show the Foo(one=1, two=2) form instead of <Foo one=1 two=2>. - attrs: The attributes to add. + **attrs: The attributes to add. """ cls = qualname(obj.__class__) parts = [] @@ -391,11 +391,10 @@ def get_repr(obj: Any, constructor: bool = False, **attrs: Any) -> str: parts.append('{}={!r}'.format(name, val)) if constructor: return '{}({})'.format(cls, ', '.join(parts)) + elif parts: + return '<{} {}>'.format(cls, ' '.join(parts)) else: - if parts: - return '<{} {}>'.format(cls, ' '.join(parts)) - else: - return '<{}>'.format(cls) + return '<{}>'.format(cls) def qualname(obj: Any) -> str: @@ -669,11 +668,12 @@ def yaml_load(f: Union[str, IO[str]]) -> Any: r"of from 'collections\.abc' is deprecated.*"): try: data = yaml.load(f, Loader=YamlLoader) - except ValueError as e: - if str(e).startswith('could not convert string to float'): + except ValueError as e: # pragma: no cover + pyyaml_error = 'could not convert string to float' + if str(e).startswith(pyyaml_error): # WORKAROUND for https://github.com/yaml/pyyaml/issues/168 raise yaml.YAMLError(e) - raise # pragma: no cover + raise end = datetime.datetime.now() @@ -784,30 +784,13 @@ def mimetype_extension(mimetype: str) -> Optional[str]: This mostly delegates to Python's mimetypes.guess_extension(), but backports some changes (via a simple override dict) which are missing from earlier Python versions. - Most likely, this can be dropped once the minimum Python version is raised to 3.7. + Most likely, this can be dropped once the minimum Python version is raised to 3.10. """ overrides = { + # Added in 3.10 + "application/x-hdf5": ".h5", # Added around 3.8 "application/manifest+json": ".webmanifest", - "application/x-hdf5": ".h5", - - # Added in Python 3.7 - "application/wasm": ".wasm", - - # Wrong values for Python 3.6 - # https://bugs.python.org/issue1043134 - # https://github.com/python/cpython/pull/14375 - "application/octet-stream": ".bin", # not .a - "application/postscript": ".ps", # not .ai - "application/vnd.ms-excel": ".xls", # not .xlb - "application/vnd.ms-powerpoint": ".ppt", # not .pot - "application/xml": ".xsl", # not .rdf - "audio/mpeg": ".mp3", # not .mp2 - "image/jpeg": ".jpg", # not .jpe - "image/tiff": ".tiff", # not .tif - "text/html": ".html", # not .htm - "text/plain": ".txt", # not .bat - "video/mpeg": ".mpeg", # not .m1v } if mimetype in overrides: return overrides[mimetype] @@ -856,3 +839,16 @@ def parse_rect(s: str) -> QRect: raise ValueError("Invalid rectangle") return rect + + +def parse_point(s: str) -> QPoint: + """Parse a point string like 13,-42.""" + try: + x, y = map(int, s.split(',', maxsplit=1)) + except ValueError: + raise ValueError(f"String {s} does not match X,Y") + + try: + return QPoint(x, y) + except OverflowError as e: + raise ValueError(e) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 8cd244fca..bf6b49fa6 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -511,10 +511,10 @@ def _get_pyqt_webengine_qt_version() -> Optional[str]: give us an accurate answer. """ try: - import importlib_metadata + import importlib.metadata as importlib_metadata # type: ignore[import] except ImportError: try: - import importlib.metadata as importlib_metadata # type: ignore[no-redef] + import importlib_metadata # type: ignore[no-redef] except ImportError: log.misc.debug("Neither importlib.metadata nor backport available") return None @@ -723,7 +723,7 @@ class WebEngineVersions: ) -def qtwebengine_versions(avoid_init: bool = False) -> WebEngineVersions: +def qtwebengine_versions(*, avoid_init: bool = False) -> WebEngineVersions: """Get the QtWebEngine and Chromium version numbers. If we have a parsed user agent, we use it here. If not, we avoid initializing @@ -773,8 +773,6 @@ def _backend() -> str: if objects.backend == usertypes.Backend.QtWebKit: return 'new QtWebKit (WebKit {})'.format(qWebKitVersion()) elif objects.backend == usertypes.Backend.QtWebEngine: - webengine = usertypes.Backend.QtWebEngine - assert objects.backend == webengine, objects.backend return str(qtwebengine_versions( avoid_init='avoid-chromium-init' in objects.debug_flags)) raise utils.Unreachable(objects.backend) diff --git a/requirements.txt b/requirements.txt index e6b75b5ef..129fe402a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,12 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -adblock==0.5.0 +adblock==0.5.2 colorama==0.4.4 -dataclasses==0.6 ; python_version<"3.7" -importlib-metadata==4.6.1 ; python_version<"3.8" -importlib-resources==5.2.0 ; python_version<"3.9" -Jinja2==3.0.1 -MarkupSafe==2.0.1 -Pygments==2.9.0 -PyYAML==5.4.1 -typing-extensions==3.10.0.0 -zipp==3.5.0 +importlib-metadata==4.11.4 ; python_version=="3.7.*" +importlib-resources==5.7.1 ; python_version=="3.7.*" or python_version=="3.8.*" +Jinja2==3.1.2 +MarkupSafe==2.1.1 +Pygments==2.12.0 +PyYAML==6.0 +typing_extensions==4.2.0 ; python_version<"3.8" +zipp==3.8.0 diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py index 0a6912b82..ba8493247 100755 --- a/scripts/asciidoc2html.py +++ b/scripts/asciidoc2html.py @@ -43,7 +43,10 @@ class AsciiDoc: """Abstraction of an asciidoc subprocess.""" - FILES = ['faq', 'changelog', 'contributing', 'quickstart', 'userscripts'] + FILES = [ + 'faq', 'changelog', 'contributing', 'quickstart', 'userscripts', + 'install', 'stacktrace' + ] def __init__(self, asciidoc: Optional[str], @@ -196,7 +199,11 @@ class AsciiDoc: continue self._build_website_file(item_path.parent, item_path.name) - copy = {'icons': 'icons', 'doc/img': 'doc/img', 'www/media': 'media/'} + copy = { + 'qutebrowser/icons': 'icons', + 'doc/img': 'doc/img', + 'www/media': 'media/', + } for src, dest in copy.items(): full_src = REPO_ROOT / src diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index a1c6646eb..a4fbadf15 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -26,6 +26,7 @@ import os.path import sys import time import shutil +import pathlib import plistlib import subprocess import argparse @@ -227,7 +228,7 @@ def patch_mac_app(): # Replace some duplicate files by symlinks framework_path = os.path.join(app_path, 'Contents', 'MacOS', 'PyQt5', - 'Qt', 'lib', 'QtWebEngineCore.framework') + 'Qt5', 'lib', 'QtWebEngineCore.framework') core_lib = os.path.join(framework_path, 'Versions', '5', 'QtWebEngineCore') os.remove(core_lib) @@ -301,7 +302,9 @@ def build_mac(*, gh_token, debug): for d in ['dist', 'build']: shutil.rmtree(d, ignore_errors=True) utils.print_title("Updating 3rdparty content") - update_3rdparty.run(ace=False, pdfjs=True, fancy_dmg=False, gh_token=gh_token) + # FIXME:qt6 Use modern PDF.js version here + update_3rdparty.run(ace=False, pdfjs=True, legacy_pdfjs=True, fancy_dmg=False, + gh_token=gh_token) utils.print_title("Building .app via pyinstaller") call_tox('pyinstaller-64', '-r', debug=debug) utils.print_title("Patching .app") @@ -394,8 +397,9 @@ def _build_windows_single(*, x64, skip_packaging, debug): def build_windows(*, gh_token, skip_packaging, only_32bit, only_64bit, debug): """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") - update_3rdparty.run(nsis=True, ace=False, pdfjs=True, fancy_dmg=False, - gh_token=gh_token) + # FIXME:qt6 Use modern PDF.js version here + update_3rdparty.run(nsis=True, ace=False, pdfjs=True, legacy_pdfjs=True, + fancy_dmg=False, gh_token=gh_token) utils.print_title("Building Windows binaries") @@ -480,24 +484,27 @@ def build_sdist(): """Build an sdist and list the contents.""" utils.print_title("Building sdist") - _maybe_remove('dist') + dist_path = pathlib.Path('dist') + _maybe_remove(dist_path) + + subprocess.run([sys.executable, '-m', 'build'], check=True) - subprocess.run([sys.executable, 'setup.py', 'sdist'], check=True) - dist_files = os.listdir(os.path.abspath('dist')) - assert len(dist_files) == 1 + dist_files = list(dist_path.glob('*.tar.gz')) + filename = 'qutebrowser-{}.tar.gz'.format(qutebrowser.__version__) + assert dist_files == [dist_path / filename], dist_files + dist_file = dist_files[0] - dist_file = os.path.join('dist', dist_files[0]) - subprocess.run(['gpg', '--detach-sign', '-a', dist_file], check=True) + subprocess.run(['gpg', '--detach-sign', '-a', str(dist_file)], check=True) - tar = tarfile.open(dist_file) by_ext = collections.defaultdict(list) - for tarinfo in tar.getmembers(): - if not tarinfo.isfile(): - continue - name = os.sep.join(tarinfo.name.split(os.sep)[1:]) - _base, ext = os.path.splitext(name) - by_ext[ext].append(name) + with tarfile.open(dist_file) as tar: + for tarinfo in tar.getmembers(): + if not tarinfo.isfile(): + continue + name = os.sep.join(tarinfo.name.split(os.sep)[1:]) + _base, ext = os.path.splitext(name) + by_ext[ext].append(name) assert '.pyc' not in by_ext @@ -507,11 +514,13 @@ def build_sdist(): utils.print_subtitle(ext) print('\n'.join(files)) - filename = 'qutebrowser-{}.tar.gz'.format(qutebrowser.__version__) artifacts = [ - (os.path.join('dist', filename), 'application/gzip', 'Source release'), - (os.path.join('dist', filename + '.asc'), 'application/pgp-signature', - 'Source release - PGP signature'), + (str(dist_file), 'application/gzip', 'Source release'), + ( + str(dist_file.with_suffix(dist_file.suffix + '.asc')), + 'application/pgp-signature', + 'Source release - PGP signature', + ), ] return artifacts @@ -550,6 +559,7 @@ def github_upload(artifacts, tag, gh_token): Args: artifacts: A list of (filename, mimetype, description) tuples tag: The name of the release tag + gh_token: The GitHub token to use """ import github3 import github3.exceptions @@ -589,9 +599,9 @@ def github_upload(artifacts, tag, gh_token): assets = [asset for asset in release.assets() if asset.name == basename] if assets: - asset = assets[0] - print("Deleting stray asset {}".format(asset.name)) - asset.delete() + stray_asset = assets[0] + print("Deleting stray asset {}".format(stray_asset.name)) + stray_asset.delete() else: break @@ -599,15 +609,19 @@ def github_upload(artifacts, tag, gh_token): def pypi_upload(artifacts): """Upload the given artifacts to PyPI using twine.""" utils.print_title("Uploading to PyPI...") - filenames = [a[0] for a in artifacts] - subprocess.run([sys.executable, '-m', 'twine', 'upload'] + filenames, - check=True) + run_twine('upload', artifacts) + + +def twine_check(artifacts): + """Check packages using 'twine check'.""" + utils.print_title("Running twine check...") + run_twine('check', artifacts, '--strict') -def upgrade_sdist_dependencies(): - """Make sure we have the latest tools for an sdist release.""" - subprocess.run([sys.executable, '-m', 'pip', 'install', '-U', 'twine', - 'pip', 'wheel', 'setuptools'], check=True) +def run_twine(command, artifacts, *args): + filenames = [a[0] for a in artifacts] + subprocess.run([sys.executable, '-m', 'twine', command] + list(args) + filenames, + check=True) def main(): @@ -667,9 +681,9 @@ def main(): elif sys.platform == 'darwin': artifacts = build_mac(gh_token=gh_token, debug=args.debug) else: - upgrade_sdist_dependencies() test_makefile() artifacts = build_sdist() + twine_check(artifacts) upload_to_pypi = True if args.upload: diff --git a/scripts/dev/change_release.py b/scripts/dev/change_release.py index 31398f9fa..b033b04ea 100644 --- a/scripts/dev/change_release.py +++ b/scripts/dev/change_release.py @@ -56,9 +56,9 @@ def change_release_description(release, filename, description): """Change a release description to the given new one.""" assets = [asset for asset in release.assets() if asset.name == filename] if not assets: - raise Error("No assets found for {filename}") + raise Error(f"No assets found for {filename}") if len(assets) > 1: - raise Error("Multiple assets found for {filename}: {assets}") + raise Error(f"Multiple assets found for {filename}: {assets}") asset = assets[0] asset.edit(filename, description) diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json new file mode 100644 index 000000000..6b33f15ef --- /dev/null +++ b/scripts/dev/changelog_urls.json @@ -0,0 +1,159 @@ +{ + "pyparsing": "https://github.com/pyparsing/pyparsing/blob/master/CHANGES", + "pylint": "https://pylint.pycqa.org/en/latest/whatsnew/2/index.html", + "tomlkit": "https://github.com/sdispater/tomlkit/blob/master/CHANGELOG.md", + "dill": "https://github.com/uqfoundation/dill/commits/master", + "isort": "https://pycqa.github.io/isort/CHANGELOG/", + "lazy-object-proxy": "https://github.com/ionelmc/python-lazy-object-proxy/blob/master/CHANGELOG.rst", + "mccabe": "https://github.com/PyCQA/mccabe#changes", + "pytest-cov": "https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst", + "pytest-xdist": "https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst", + "pytest-forked": "https://github.com/pytest-dev/pytest-forked/blob/master/CHANGELOG.rst", + "pytest-xvfb": "https://github.com/The-Compiler/pytest-xvfb/blob/master/CHANGELOG.rst", + "PyVirtualDisplay": "https://github.com/ponty/PyVirtualDisplay/commits/master", + "execnet": "https://execnet.readthedocs.io/en/latest/changelog.html", + "pytest-rerunfailures": "https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst", + "pytest-repeat": "https://github.com/pytest-dev/pytest-repeat/blob/master/CHANGES.rst", + "requests": "https://github.com/psf/requests/blob/main/HISTORY.md", + "requests-file": "https://github.com/dashea/requests-file/blob/master/CHANGES.rst", + "Werkzeug": "https://werkzeug.palletsprojects.com/en/latest/changes/", + "click": "https://click.palletsprojects.com/en/latest/changes/", + "itsdangerous": "https://itsdangerous.palletsprojects.com/en/latest/changes/", + "parse-type": "https://github.com/jenisys/parse_type/blob/master/CHANGES.txt", + "sortedcontainers": "https://github.com/grantjenks/python-sortedcontainers/blob/master/HISTORY.rst", + "soupsieve": "https://facelessuser.github.io/soupsieve/about/changelog/", + "Flask": "https://flask.palletsprojects.com/en/latest/changes/", + "Mako": "https://docs.makotemplates.org/en/latest/changelog.html", + "glob2": "https://github.com/miracle2k/python-glob2/blob/master/CHANGES", + "hypothesis": "https://hypothesis.readthedocs.io/en/latest/changes.html", + "exceptiongroup": "https://github.com/agronholm/exceptiongroup/blob/main/CHANGES.rst", + "mypy": "https://mypy-lang.blogspot.com/", + "types-PyYAML": "https://github.com/python/typeshed/commits/master/stubs/PyYAML", + "pytest": "https://docs.pytest.org/en/latest/changelog.html", + "iniconfig": "https://github.com/RonnyPfannschmidt/iniconfig/blob/master/CHANGELOG", + "tox": "https://tox.readthedocs.io/en/latest/changelog.html", + "PyYAML": "https://github.com/yaml/pyyaml/blob/master/CHANGES", + "pytest-bdd": "https://github.com/pytest-dev/pytest-bdd/blob/master/CHANGES.rst", + "snowballstemmer": "https://github.com/snowballstem/snowball/blob/master/NEWS", + "virtualenv": "https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst", + "packaging": "https://packaging.pypa.io/en/latest/changelog.html", + "build": "https://github.com/pypa/build/blob/main/CHANGELOG.rst", + "attrs": "https://www.attrs.org/en/stable/changelog.html", + "Jinja2": "https://jinja.palletsprojects.com/en/latest/changes/", + "MarkupSafe": "https://markupsafe.palletsprojects.com/en/latest/changes/", + "flake8": "https://github.com/PyCQA/flake8/tree/main/docs/source/release-notes", + "flake8-docstrings": "https://pypi.org/project/flake8-docstrings/", + "flake8-debugger": "https://github.com/JBKahn/flake8-debugger/", + "flake8-builtins": "https://github.com/gforcada/flake8-builtins/blob/master/CHANGES.rst", + "flake8-bugbear": "https://github.com/PyCQA/flake8-bugbear#change-log", + "flake8-tidy-imports": "https://github.com/adamchainz/flake8-tidy-imports/blob/master/HISTORY.rst", + "flake8-tuple": "https://github.com/ar4s/flake8_tuple/blob/master/HISTORY.rst", + "flake8-comprehensions": "https://github.com/adamchainz/flake8-comprehensions/blob/master/HISTORY.rst", + "flake8-copyright": "https://github.com/savoirfairelinux/flake8-copyright/blob/master/CHANGELOG.rst", + "flake8-deprecated": "https://github.com/gforcada/flake8-deprecated/blob/master/CHANGES.rst", + "flake8-future-import": "https://github.com/xZise/flake8-future-import#changes", + "flake8-mock": "https://github.com/aleGpereira/flake8-mock#changes", + "flake8-string-format": "https://github.com/xZise/flake8-string-format#changes", + "flake8-plugin-utils": "https://github.com/afonasev/flake8-plugin-utils#change-log", + "flake8-pytest-style": "https://github.com/m-burst/flake8-pytest-style#change-log", + "pep8-naming": "https://github.com/PyCQA/pep8-naming/blob/master/CHANGELOG.rst", + "pycodestyle": "https://github.com/PyCQA/pycodestyle/blob/master/CHANGES.txt", + "pyflakes": "https://github.com/PyCQA/pyflakes/blob/master/NEWS.rst", + "cffi": "https://github.com/python-cffi/release-doc/blob/master/doc/source/whatsnew.rst", + "astroid": "https://github.com/PyCQA/astroid/blob/main/ChangeLog", + "pytest-instafail": "https://github.com/pytest-dev/pytest-instafail/blob/master/CHANGES.rst", + "coverage": "https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst", + "colorama": "https://github.com/tartley/colorama/blob/master/CHANGELOG.rst", + "hunter": "https://github.com/ionelmc/python-hunter/blob/master/CHANGELOG.rst", + "uritemplate": "https://github.com/python-hyper/uritemplate/blob/master/HISTORY.rst", + "more-itertools": "https://github.com/erikrose/more-itertools/blob/master/docs/versions.rst", + "pydocstyle": "https://www.pydocstyle.org/en/latest/release_notes.html", + "Sphinx": "https://www.sphinx-doc.org/en/master/changes.html", + "Babel": "https://github.com/python-babel/babel/blob/master/CHANGES.rst", + "alabaster": "https://alabaster.readthedocs.io/en/latest/changelog.html", + "imagesize": "https://github.com/shibukawa/imagesize_py/commits/master", + "pytz": "https://mm.icann.org/pipermail/tz-announce/", + "sphinxcontrib-applehelp": "https://www.sphinx-doc.org/en/master/changes.html", + "sphinxcontrib-devhelp": "https://www.sphinx-doc.org/en/master/changes.html", + "sphinxcontrib-htmlhelp": "https://www.sphinx-doc.org/en/master/changes.html", + "sphinxcontrib-jsmath": "https://www.sphinx-doc.org/en/master/changes.html", + "sphinxcontrib-qthelp": "https://www.sphinx-doc.org/en/master/changes.html", + "sphinxcontrib-serializinghtml": "https://www.sphinx-doc.org/en/master/changes.html", + "jaraco.functools": "https://github.com/jaraco/jaraco.functools/blob/master/CHANGES.rst", + "parse": "https://github.com/r1chardj0n3s/parse#potential-gotchas", + "py": "https://py.readthedocs.io/en/latest/changelog.html#changelog", + "Pympler": "https://github.com/pympler/pympler/blob/master/CHANGELOG.md", + "pytest-mock": "https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst", + "pytest-qt": "https://github.com/pytest-dev/pytest-qt/blob/master/CHANGELOG.rst", + "pyinstaller": "https://pyinstaller.readthedocs.io/en/stable/CHANGES.html", + "pyinstaller-hooks-contrib": "https://github.com/pyinstaller/pyinstaller-hooks-contrib/blob/master/CHANGELOG.rst", + "pytest-benchmark": "https://pytest-benchmark.readthedocs.io/en/stable/changelog.html", + "typed-ast": "https://github.com/python/typed_ast/commits/master", + "docutils": "https://docutils.sourceforge.io/RELEASE-NOTES.html", + "bump2version": "https://github.com/c4urself/bump2version/blob/master/CHANGELOG.md", + "six": "https://github.com/benjaminp/six/blob/master/CHANGES", + "altgraph": "https://github.com/ronaldoussoren/altgraph/blob/master/doc/changelog.rst", + "urllib3": "https://github.com/urllib3/urllib3/blob/master/CHANGES.rst", + "lxml": "https://github.com/lxml/lxml/blob/master/CHANGES.txt", + "wrapt": "https://github.com/GrahamDumpleton/wrapt/blob/develop/docs/changes.rst", + "pep517": "https://github.com/pypa/pep517/blob/master/doc/changelog.rst", + "cryptography": "https://cryptography.io/en/latest/changelog.html", + "toml": "https://github.com/uiri/toml/releases", + "tomli": "https://github.com/hukkin/tomli/blob/master/CHANGELOG.md", + "PyQt5": "https://www.riverbankcomputing.com/news", + "PyQt5-Qt5": "https://www.riverbankcomputing.com/news", + "PyQtWebEngine": "https://www.riverbankcomputing.com/news", + "PyQtWebEngine-Qt5": "https://www.riverbankcomputing.com/news", + "PyQt-builder": "https://www.riverbankcomputing.com/news", + "PyQt5-sip": "https://www.riverbankcomputing.com/news", + "PyQt5-stubs": "https://github.com/stlehmann/PyQt5-stubs/blob/master/CHANGELOG.md", + "sip": "https://www.riverbankcomputing.com/news", + "Pygments": "https://pygments.org/docs/changelog/", + "vulture": "https://github.com/jendrikseipp/vulture/blob/master/CHANGELOG.md", + "distlib": "https://github.com/pypa/distlib/blob/master/CHANGES.rst", + "py-cpuinfo": "https://github.com/workhorsy/py-cpuinfo/blob/master/ChangeLog", + "cheroot": "https://cheroot.cherrypy.dev/en/latest/history.html", + "certifi": "https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReport", + "chardet": "https://github.com/chardet/chardet/releases", + "charset-normalizer": "https://github.com/Ousret/charset_normalizer/blob/master/CHANGELOG.md", + "idna": "https://github.com/kjd/idna/blob/master/HISTORY.rst", + "tldextract": "https://github.com/john-kurkowski/tldextract/blob/master/CHANGELOG.md", + "typing_extensions": "https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG", + "diff-cover": "https://github.com/Bachmann1234/diff_cover/blob/master/CHANGELOG", + "beautifulsoup4": "https://bazaar.launchpad.net/~leonardr/beautifulsoup/bs4/view/head:/CHANGELOG", + "check-manifest": "https://github.com/mgedmin/check-manifest/blob/master/CHANGES.rst", + "yamllint": "https://github.com/adrienverge/yamllint/blob/master/CHANGELOG.rst", + "pathspec": "https://github.com/cpburnz/python-path-specification/blob/master/CHANGES.rst", + "filelock": "https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst", + "github3.py": "https://github3.readthedocs.io/en/latest/release-notes/index.html", + "manhole": "https://github.com/ionelmc/python-manhole/blob/master/CHANGELOG.rst", + "pycparser": "https://github.com/eliben/pycparser/blob/master/CHANGES", + "python-dateutil": "https://dateutil.readthedocs.io/en/stable/changelog.html", + "platformdirs": "https://github.com/platformdirs/platformdirs/blob/main/CHANGES.rst", + "pluggy": "https://github.com/pytest-dev/pluggy/blob/master/CHANGELOG.rst", + "mypy-extensions": "https://github.com/python/mypy_extensions/commits/master", + "pyroma": "https://github.com/regebro/pyroma/blob/master/CHANGES.txt", + "adblock": "https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md", + "importlib-resources": "https://importlib-resources.readthedocs.io/en/latest/history.html", + "importlib-metadata": "https://github.com/python/importlib_metadata/blob/main/CHANGES.rst", + "zipp": "https://github.com/jaraco/zipp/blob/main/CHANGES.rst", + "pip": "https://pip.pypa.io/en/stable/news/", + "wheel": "https://wheel.readthedocs.io/en/stable/news.html", + "setuptools": "https://setuptools.readthedocs.io/en/latest/history.html", + "future": "https://python-future.org/whatsnew.html", + "pefile": "https://github.com/erocarrera/pefile/commits/master", + "SecretStorage": "https://github.com/mitya57/secretstorage/blob/master/changelog", + "bleach": "https://github.com/mozilla/bleach/blob/main/CHANGES", + "jeepney": "https://gitlab.com/takluyver/jeepney/-/blob/master/docs/release-notes.rst", + "keyring": "https://github.com/jaraco/keyring/blob/main/CHANGES.rst", + "pkginfo": "https://bazaar.launchpad.net/~tseaver/pkginfo/trunk/view/head:/CHANGES.txt", + "readme-renderer": "https://github.com/pypa/readme_renderer/blob/main/CHANGES.rst", + "requests-toolbelt": "https://github.com/requests/toolbelt/blob/master/HISTORY.rst", + "rfc3986": "https://rfc3986.readthedocs.io/en/latest/release-notes/index.html", + "twine": "https://twine.readthedocs.io/en/stable/changelog.html", + "webencodings": "https://github.com/gsnedders/python-webencodings/commits/master", + "PyJWT": "https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst", + "commonmark": "https://github.com/readthedocs/commonmark.py/blob/master/CHANGELOG.md", + "rich": "https://github.com/Textualize/rich/blob/master/CHANGELOG.md", + "ply": "https://github.com/dabeaz/ply/blob/master/CHANGES" +} diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index c66cb3e8d..8f1d2df2b 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -333,7 +333,7 @@ def main_check(): subprocess.run([sys.executable, '-m', 'coverage', 'report', '--show-missing', '--include', filters], check=True) print() - print("To debug this, run 'tox -e py36-pyqt515-cov' " + print("To debug this, run 'tox -e py39-pyqt515-cov' " "(replace Python/Qt versions based on your system) locally and check " "htmlcov/index.html") print("or check https://codecov.io/github/qutebrowser/qutebrowser") @@ -353,7 +353,7 @@ def main_check_all(): tests. This runs pytest with the used executable, so check_coverage.py should be - called with something like ./.tox/py36/bin/python. + called with something like ./.tox/py39/bin/python. """ for test_file, src_file in PERFECT_FILES: if test_file is None: diff --git a/scripts/dev/gen_resources.py b/scripts/dev/gen_resources.py deleted file mode 100644 index dd76c123e..000000000 --- a/scripts/dev/gen_resources.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# Copyright 2014-2021 Florian Bruhin (The-Compiler) <me@the-compiler.org> - -# this file is part of qutebrowser. -# -# qutebrowser is free software: you can redistribute it and/or modify -# it under the terms of the gnu general public license as published by -# the free software foundation, either version 3 of the license, or -# (at your option) any later version. -# -# qutebrowser is distributed in the hope that it will be useful, -# but without any warranty; without even the implied warranty of -# merchantability or fitness for a particular purpose. see the -# gnu general public license for more details. -# -# you should have received a copy of the gnu general public license -# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. - -"""Generate Qt resources based on source files.""" - -import subprocess - -with open('qutebrowser/resources.py', 'w', encoding='utf-8') as f: - subprocess.run(['pyrcc5', 'qutebrowser.rcc'], stdout=f, check=True) diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py index bae51e372..908daad4d 100644 --- a/scripts/dev/misc_checks.py +++ b/scripts/dev/misc_checks.py @@ -50,7 +50,7 @@ def _get_files( filenames = subprocess.run( ['git', 'ls-files', '--cached', '--others', '--exclude-standard', '-z'], stdout=subprocess.PIPE, - universal_newlines=True, + text=True, check=True, ) all_ignored = ignored or [] @@ -111,7 +111,7 @@ def check_changelog_urls(_args: argparse.Namespace = None) -> bool: utils.print_col(f"Extra changelog URLs: {req_str}", 'red') if not ok: - print("Hint: Changelog URLs are in scripts/dev/recompile_requirements.py") + print("Hint: Changelog URLs are in scripts/dev/changelog_urls.json") return ok @@ -199,11 +199,6 @@ def check_spelling(args: argparse.Namespace) -> Optional[bool]: "Don't use a blanket 'noqa', use something like 'noqa: X123' instead.", ), ( - re.compile(r'# type: ignore[^\[]'), - ("Don't use a blanket 'type: ignore', use something like " - "'type: ignore[error-code]' instead."), - ), - ( re.compile(r'# type: (?!ignore(\[|$))'), "Don't use type comments, use type annotations instead.", ), @@ -342,7 +337,7 @@ def check_userscripts_descriptions(_args: argparse.Namespace = None) -> bool: def check_userscript_shebangs(_args: argparse.Namespace) -> bool: - """Check that we're using /usr/bin/env in shebangs.""" + """Check that we're using /usr/bin/env in shebangs and scripts are executable.""" ok = True folder = pathlib.Path('misc/userscripts') @@ -350,6 +345,10 @@ def check_userscript_shebangs(_args: argparse.Namespace) -> bool: if sub.is_dir() or sub.name == 'README.md': continue + if not os.access(sub, os.X_OK): + print(f"{sub} is not marked as executable") + ok = False + with sub.open('r', encoding='utf-8') as f: shebang = f.readline().rstrip('\n') assert shebang.startswith('#!'), shebang diff --git a/scripts/dev/pylint_checkers/qute_pylint/modeline.py b/scripts/dev/pylint_checkers/qute_pylint/modeline.py index 114cfaf94..1df2c375e 100644 --- a/scripts/dev/pylint_checkers/qute_pylint/modeline.py +++ b/scripts/dev/pylint_checkers/qute_pylint/modeline.py @@ -31,9 +31,9 @@ class ModelineChecker(checkers.BaseChecker): __implements__ = interfaces.IRawChecker name = 'modeline' - msgs = {'W9002': ('Does not have vim modeline', 'modeline-missing', None), - 'W9003': ('Modeline is invalid', 'invalid-modeline', None), - 'W9004': ('Modeline position is wrong', 'modeline-position', None)} + msgs = {'W9102': ('Does not have vim modeline', 'modeline-missing', None), + 'W9103': ('Modeline is invalid', 'invalid-modeline', None), + 'W9104': ('Modeline position is wrong', 'modeline-position', None)} options = () priority = -1 diff --git a/scripts/dev/pylint_checkers/qute_pylint/openencoding.py b/scripts/dev/pylint_checkers/qute_pylint/openencoding.py deleted file mode 100644 index 972a55db8..000000000 --- a/scripts/dev/pylint_checkers/qute_pylint/openencoding.py +++ /dev/null @@ -1,83 +0,0 @@ -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# Copyright 2014-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org> - -# This file is part of qutebrowser. -# -# qutebrowser is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# qutebrowser is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. - -"""Make sure open() has an encoding set.""" - -import astroid -from pylint import interfaces, checkers -from pylint.checkers import utils - - -class OpenEncodingChecker(checkers.BaseChecker): - - """Checker to check open() has an encoding set.""" - - __implements__ = interfaces.IAstroidChecker - name = 'open-encoding' - - msgs = { - 'W9400': ('open() called without encoding', 'open-without-encoding', - None), - } - - @utils.check_messages('open-without-encoding') - def visit_call(self, node): - """Visit a Call node.""" - if hasattr(node, 'func'): - infer = utils.safe_infer(node.func) - if infer and infer.root().name == '_io': - if getattr(node.func, 'name', None) in ['open', 'file']: - self._check_open_encoding(node) - - def _check_open_encoding(self, node): - """Check that an open() call always has an encoding set.""" - try: - mode_arg = utils.get_argument_from_call(node, position=1, - keyword='mode') - except utils.NoSuchArgumentError: - mode_arg = None - _encoding = None - try: - _encoding = utils.get_argument_from_call(node, position=2) - except utils.NoSuchArgumentError: - try: - _encoding = utils.get_argument_from_call(node, - keyword='encoding') - except utils.NoSuchArgumentError: - pass - if _encoding is None: - if mode_arg is None: - mode = None - else: - mode = utils.safe_infer(mode_arg) - if mode is not None and not isinstance(mode, astroid.Const): - # We can't say what mode is exactly. - return - if mode is None: - self.add_message('open-without-encoding', node=node) - elif 'b' in getattr(mode, 'value', ''): - # Files opened as binary don't need an encoding. - return - else: - self.add_message('open-without-encoding', node=node) - - -def register(linter): - """Register this checker.""" - linter.register_checker(OpenEncodingChecker(linter)) diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py index 158741e5c..365f9a51e 100644 --- a/scripts/dev/recompile_requirements.py +++ b/scripts/dev/recompile_requirements.py @@ -24,9 +24,12 @@ import re import sys import os.path import glob +import json import subprocess import tempfile import argparse +import shutil +import pathlib sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) @@ -37,157 +40,8 @@ REPO_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..') # /scripts/dev -> /scripts -> / REQ_DIR = os.path.join(REPO_DIR, 'misc', 'requirements') -CHANGELOG_URLS = { - 'pyparsing': 'https://github.com/pyparsing/pyparsing/blob/master/CHANGES', - 'pylint': 'https://pylint.pycqa.org/en/latest/whatsnew/changelog.html', - 'isort': 'https://pycqa.github.io/isort/CHANGELOG/', - 'lazy-object-proxy': 'https://github.com/ionelmc/python-lazy-object-proxy/blob/master/CHANGELOG.rst', - 'mccabe': 'https://github.com/PyCQA/mccabe#changes', - 'pytest-cov': 'https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst', - 'pytest-xdist': 'https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst', - 'pytest-forked': 'https://github.com/pytest-dev/pytest-forked/blob/master/CHANGELOG', - 'pytest-xvfb': 'https://github.com/The-Compiler/pytest-xvfb/blob/master/CHANGELOG.rst', - 'EasyProcess': 'https://github.com/ponty/EasyProcess/commits/master', - 'PyVirtualDisplay': 'https://github.com/ponty/PyVirtualDisplay/commits/master', - 'execnet': 'https://execnet.readthedocs.io/en/latest/changelog.html', - 'pytest-rerunfailures': 'https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst', - 'pytest-repeat': 'https://github.com/pytest-dev/pytest-repeat/blob/master/CHANGES.rst', - 'requests': 'https://github.com/psf/requests/blob/master/HISTORY.md', - 'requests-file': 'https://github.com/dashea/requests-file/blob/master/CHANGES.rst', - 'Werkzeug': 'https://werkzeug.palletsprojects.com/en/latest/changes/', - 'click': 'https://click.palletsprojects.com/en/latest/changes/', - 'itsdangerous': 'https://itsdangerous.palletsprojects.com/en/latest/changes/', - 'parse-type': 'https://github.com/jenisys/parse_type/blob/master/CHANGES.txt', - 'sortedcontainers': 'https://github.com/grantjenks/python-sortedcontainers/blob/master/HISTORY.rst', - 'soupsieve': 'https://facelessuser.github.io/soupsieve/about/changelog/', - 'Flask': 'https://flask.palletsprojects.com/en/latest/changes/', - 'Mako': 'https://docs.makotemplates.org/en/latest/changelog.html', - 'glob2': 'https://github.com/miracle2k/python-glob2/blob/master/CHANGES', - 'hypothesis': 'https://hypothesis.readthedocs.io/en/latest/changes.html', - 'mypy': 'https://mypy-lang.blogspot.com/', - 'types-PyYAML': 'https://github.com/python/typeshed/commits/master/stubs/PyYAML', - 'types-dataclasses': 'https://github.com/python/typeshed/commits/master/stubs/dataclasses', - 'pytest': 'https://docs.pytest.org/en/latest/changelog.html', - 'iniconfig': 'https://github.com/RonnyPfannschmidt/iniconfig/blob/master/CHANGELOG', - 'tox': 'https://tox.readthedocs.io/en/latest/changelog.html', - 'PyYAML': 'https://github.com/yaml/pyyaml/blob/master/CHANGES', - 'pytest-bdd': 'https://github.com/pytest-dev/pytest-bdd/blob/master/CHANGES.rst', - 'snowballstemmer': 'https://github.com/snowballstem/snowball/blob/master/NEWS', - 'virtualenv': 'https://virtualenv.pypa.io/en/latest/changelog.html', - 'packaging': 'https://packaging.pypa.io/en/latest/changelog.html', - 'build': 'https://github.com/pypa/build/blob/main/CHANGELOG.rst', - 'attrs': 'https://www.attrs.org/en/stable/changelog.html', - 'Jinja2': 'https://jinja.palletsprojects.com/en/latest/changes/', - 'MarkupSafe': 'https://markupsafe.palletsprojects.com/en/latest/changes/', - 'flake8': 'https://gitlab.com/pycqa/flake8/tree/master/docs/source/release-notes', - 'flake8-docstrings': 'https://pypi.org/project/flake8-docstrings/', - 'flake8-debugger': 'https://github.com/JBKahn/flake8-debugger/', - 'flake8-builtins': 'https://github.com/gforcada/flake8-builtins/blob/master/CHANGES.rst', - 'flake8-bugbear': 'https://github.com/PyCQA/flake8-bugbear#change-log', - 'flake8-tidy-imports': 'https://github.com/adamchainz/flake8-tidy-imports/blob/master/HISTORY.rst', - 'flake8-tuple': 'https://github.com/ar4s/flake8_tuple/blob/master/HISTORY.rst', - 'flake8-comprehensions': 'https://github.com/adamchainz/flake8-comprehensions/blob/master/HISTORY.rst', - 'flake8-copyright': 'https://github.com/savoirfairelinux/flake8-copyright/blob/master/CHANGELOG.rst', - 'flake8-deprecated': 'https://github.com/gforcada/flake8-deprecated/blob/master/CHANGES.rst', - 'flake8-future-import': 'https://github.com/xZise/flake8-future-import#changes', - 'flake8-mock': 'https://github.com/aleGpereira/flake8-mock#changes', - 'flake8-polyfill': 'https://gitlab.com/pycqa/flake8-polyfill/-/blob/master/CHANGELOG.rst', - 'flake8-string-format': 'https://github.com/xZise/flake8-string-format#changes', - 'pep8-naming': 'https://github.com/PyCQA/pep8-naming/blob/master/CHANGELOG.rst', - 'pycodestyle': 'https://github.com/PyCQA/pycodestyle/blob/master/CHANGES.txt', - 'pyflakes': 'https://github.com/PyCQA/pyflakes/blob/master/NEWS.rst', - 'cffi': 'https://github.com/python-cffi/release-doc/blob/master/doc/source/whatsnew.rst', - 'astroid': 'https://github.com/PyCQA/astroid/blob/2.4/ChangeLog', - 'pytest-instafail': 'https://github.com/pytest-dev/pytest-instafail/blob/master/CHANGES.rst', - 'coverage': 'https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst', - 'colorama': 'https://github.com/tartley/colorama/blob/master/CHANGELOG.rst', - 'hunter': 'https://github.com/ionelmc/python-hunter/blob/master/CHANGELOG.rst', - 'uritemplate': 'https://github.com/python-hyper/uritemplate/blob/master/HISTORY.rst', - 'more-itertools': 'https://github.com/erikrose/more-itertools/blob/master/docs/versions.rst', - 'pydocstyle': 'https://www.pydocstyle.org/en/latest/release_notes.html', - 'Sphinx': 'https://www.sphinx-doc.org/en/master/changes.html', - 'Babel': 'https://github.com/python-babel/babel/blob/master/CHANGES', - 'alabaster': 'https://alabaster.readthedocs.io/en/latest/changelog.html', - 'imagesize': 'https://github.com/shibukawa/imagesize_py/commits/master', - 'pytz': 'https://mm.icann.org/pipermail/tz-announce/', - 'sphinxcontrib-applehelp': 'https://www.sphinx-doc.org/en/master/changes.html', - 'sphinxcontrib-devhelp': 'https://www.sphinx-doc.org/en/master/changes.html', - 'sphinxcontrib-htmlhelp': 'https://www.sphinx-doc.org/en/master/changes.html', - 'sphinxcontrib-jsmath': 'https://www.sphinx-doc.org/en/master/changes.html', - 'sphinxcontrib-qthelp': 'https://www.sphinx-doc.org/en/master/changes.html', - 'sphinxcontrib-serializinghtml': 'https://www.sphinx-doc.org/en/master/changes.html', - 'jaraco.functools': 'https://github.com/jaraco/jaraco.functools/blob/master/CHANGES.rst', - 'parse': 'https://github.com/r1chardj0n3s/parse#potential-gotchas', - 'py': 'https://py.readthedocs.io/en/latest/changelog.html#changelog', - 'Pympler': 'https://github.com/pympler/pympler/blob/master/CHANGELOG.md', - 'pytest-mock': 'https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst', - 'pytest-qt': 'https://github.com/pytest-dev/pytest-qt/blob/master/CHANGELOG.rst', - 'pyinstaller': 'https://pyinstaller.readthedocs.io/en/stable/CHANGES.html', - 'pyinstaller-hooks-contrib': 'https://github.com/pyinstaller/pyinstaller-hooks-contrib/blob/master/CHANGELOG.rst', - 'pytest-benchmark': 'https://pytest-benchmark.readthedocs.io/en/stable/changelog.html', - 'typed-ast': 'https://github.com/python/typed_ast/commits/master', - 'docutils': 'https://docutils.sourceforge.io/RELEASE-NOTES.html', - 'bump2version': 'https://github.com/c4urself/bump2version/blob/master/CHANGELOG.md', - 'six': 'https://github.com/benjaminp/six/blob/master/CHANGES', - 'altgraph': 'https://github.com/ronaldoussoren/altgraph/blob/master/doc/changelog.rst', - 'urllib3': 'https://github.com/urllib3/urllib3/blob/master/CHANGES.rst', - 'lxml': 'https://lxml.de/index.html#old-versions', - 'jwcrypto': 'https://github.com/latchset/jwcrypto/commits/master', - 'wrapt': 'https://github.com/GrahamDumpleton/wrapt/blob/develop/docs/changes.rst', - 'pep517': 'https://github.com/pypa/pep517/blob/master/doc/changelog.rst', - 'cryptography': 'https://cryptography.io/en/latest/changelog.html', - 'toml': 'https://github.com/uiri/toml/releases', - 'PyQt5': 'https://www.riverbankcomputing.com/news', - 'PyQt5-Qt': 'https://www.riverbankcomputing.com/news', - 'PyQt5-Qt5': 'https://www.riverbankcomputing.com/news', - 'PyQtWebEngine': 'https://www.riverbankcomputing.com/news', - 'PyQtWebEngine-Qt': 'https://www.riverbankcomputing.com/news', - 'PyQtWebEngine-Qt5': 'https://www.riverbankcomputing.com/news', - 'PyQt-builder': 'https://www.riverbankcomputing.com/news', - 'PyQt5-sip': 'https://www.riverbankcomputing.com/news', - 'PyQt5-stubs': 'https://github.com/stlehmann/PyQt5-stubs/blob/master/CHANGELOG.md', - 'sip': 'https://www.riverbankcomputing.com/news', - 'Pygments': 'https://pygments.org/docs/changelog/', - 'vulture': 'https://github.com/jendrikseipp/vulture/blob/master/CHANGELOG.md', - 'distlib': 'https://bitbucket.org/pypa/distlib/src/master/CHANGES.rst', - 'py-cpuinfo': 'https://github.com/workhorsy/py-cpuinfo/blob/master/ChangeLog', - 'cheroot': 'https://cheroot.cherrypy.org/en/latest/history.html', - 'certifi': 'https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReport', - 'chardet': 'https://github.com/chardet/chardet/releases', - 'idna': 'https://github.com/kjd/idna/blob/master/HISTORY.rst', - 'tldextract': 'https://github.com/john-kurkowski/tldextract/blob/master/CHANGELOG.md', - 'typing-extensions': 'https://github.com/python/typing/commits/master/typing_extensions', - 'diff-cover': 'https://github.com/Bachmann1234/diff_cover/blob/master/CHANGELOG', - 'pytest-icdiff': 'https://github.com/hjwp/pytest-icdiff/blob/master/HISTORY.rst', - 'icdiff': 'https://github.com/jeffkaufman/icdiff/blob/master/ChangeLog', - 'pprintpp': 'https://github.com/wolever/pprintpp/blob/master/CHANGELOG.txt', - 'beautifulsoup4': 'https://bazaar.launchpad.net/~leonardr/beautifulsoup/bs4/view/head:/CHANGELOG', - 'check-manifest': 'https://github.com/mgedmin/check-manifest/blob/master/CHANGES.rst', - 'yamllint': 'https://github.com/adrienverge/yamllint/blob/master/CHANGELOG.rst', - 'pathspec': 'https://github.com/cpburnz/python-path-specification/blob/master/CHANGES.rst', - 'filelock': 'https://github.com/benediktschmitt/py-filelock/commits/master', - 'github3.py': 'https://github3py.readthedocs.io/en/master/release-notes/index.html', - 'manhole': 'https://github.com/ionelmc/python-manhole/blob/master/CHANGELOG.rst', - 'pycparser': 'https://github.com/eliben/pycparser/blob/master/CHANGES', - 'python-dateutil': 'https://dateutil.readthedocs.io/en/stable/changelog.html', - 'appdirs': 'https://github.com/ActiveState/appdirs/blob/master/CHANGES.rst', - 'pluggy': 'https://github.com/pytest-dev/pluggy/blob/master/CHANGELOG.rst', - 'inflect': 'https://github.com/jazzband/inflect/blob/master/CHANGES.rst', - 'jinja2-pluralize': 'https://github.com/audreyfeldroy/jinja2_pluralize/blob/master/HISTORY.rst', - 'mypy-extensions': 'https://github.com/python/mypy_extensions/commits/master', - 'pyroma': 'https://github.com/regebro/pyroma/blob/master/HISTORY.txt', - 'adblock': 'https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md', - 'importlib-resources': 'https://importlib-resources.readthedocs.io/en/latest/history.html', - 'importlib-metadata': 'https://github.com/python/importlib_metadata/blob/main/CHANGES.rst', - 'zipp': 'https://github.com/jaraco/zipp/blob/main/CHANGES.rst', - 'dataclasses': 'https://github.com/ericvsmith/dataclasses#release-history', - 'pip': 'https://pip.pypa.io/en/stable/news/', - 'wheel': 'https://wheel.readthedocs.io/en/stable/news.html', - 'setuptools': 'https://setuptools.readthedocs.io/en/latest/history.html', - 'future': 'https://python-future.org/whatsnew.html', - 'pefile': 'https://github.com/erocarrera/pefile/commits/master', - 'Deprecated': 'https://github.com/tantale/deprecated/blob/master/CHANGELOG.rst', -} +CHANGELOG_URLS_PATH = pathlib.Path(__file__).parent / "changelog_urls.json" +CHANGELOG_URLS = json.loads(CHANGELOG_URLS_PATH.read_text()) def convert_line(line, comments): @@ -235,6 +89,7 @@ def read_comments(fobj): 'add': [], 'replace': {}, 'pre': False, + 'pip_args': [], } for line in fobj: if line.startswith('#@'): @@ -264,6 +119,8 @@ def read_comments(fobj): comments['add'].append(args) elif command == 'pre': comments['pre'] = True + elif command == 'pip_args': + comments['pip_args'] += args.split() return comments @@ -287,7 +144,7 @@ def run_pip(venv_dir, *args, quiet=False, **kwargs): return subprocess.run([venv_python, '-m', 'pip'] + args, check=True, **kwargs) -def init_venv(host_python, venv_dir, requirements, pre=False): +def init_venv(host_python, venv_dir, requirements, pre=False, pip_args=None): """Initialize a new virtualenv and install the given packages.""" with utils.gha_group('Creating virtualenv'): utils.print_col('$ python3 -m venv {}'.format(venv_dir), 'blue') @@ -299,6 +156,8 @@ def init_venv(host_python, venv_dir, requirements, pre=False): install_command = ['install', '-r', requirements] if pre: install_command.append('--pre') + if pip_args: + install_command += pip_args with utils.gha_group('Installing requirements'): run_pip(venv_dir, *install_command) @@ -329,10 +188,11 @@ class Change: """A single requirements change from a git diff output.""" - def __init__(self, name): + def __init__(self, name: str, base_path: pathlib.Path) -> None: self.name = name self.old = None self.new = None + self.base = extract_requirement_name(base_path) if CHANGELOG_URLS.get(name): self.url = CHANGELOG_URLS[name] self.link = '[{}]({})'.format(self.name, self.url) @@ -340,24 +200,27 @@ class Change: self.url = '(no changelog)' self.link = self.name + def __lt__(self, other): + return (self.base, self.name.lower()) < (other.base, other.name.lower()) + def __str__(self): + prefix = f"- [{self.base}] {self.name}" + suffix = f" {self.url}" if self.old is None: - return '- {} new: {} {}'.format(self.name, self.new, self.url) + return f"{prefix} new: {self.new} {suffix}" elif self.new is None: - return '- {} removed: {} {}'.format(self.name, self.old, - self.url) + return f"{prefix} removed: {self.old} {suffix}" else: - return '- {} {} -> {} {}'.format(self.name, self.old, self.new, - self.url) + return f"{prefix} {self.old} -> {self.new} {suffix}" def table_str(self): """Generate a markdown table.""" if self.old is None: - return '| {} | -- | {} |'.format(self.link, self.new) + return f'| {self.base} | {self.link} | -- | {self.new} |' elif self.new is None: - return '| {} | {} | -- |'.format(self.link, self.old) + return f'| {self.base} | {self.link} | {self.old} | -- |' else: - return '| {} | {} | {} |'.format(self.link, self.old, self.new) + return f'| {self.base} | {self.link} | {self.old} | {self.new} |' def _get_changed_files(): @@ -365,25 +228,43 @@ def _get_changed_files(): changed_files = set() filenames = git_diff('--name-only') for filename in filenames: - filename = filename.strip() - filename = filename.replace('misc/requirements/requirements-', '') - filename = filename.replace('.txt', '') - changed_files.add(filename) + requirement_name = extract_requirement_name(pathlib.Path(filename)) + changed_files.add(requirement_name) return sorted(changed_files) +def extract_requirement_name(path: pathlib.Path) -> str: + """Get a requirement name from a file path. + + e.g. "pylint" from "misc/requirements/requirements-pylint.txt" + """ + if path == pathlib.Path("requirements.txt"): + return "qutebrowser" + + prefix = "requirements-" + assert path.suffix == ".txt", path + assert path.stem.startswith(prefix), path + return path.stem[len(prefix):] + + def parse_versioned_line(line): """Parse a requirements.txt line into name/version.""" - if '==' in line: - if line[0] == '#': # ignored dependency - line = line[1:].strip() + if line[0] == '#': # ignored dependency + line = line[1:].strip() + + # Strip comments and pip environment markers + line = line.rsplit('#', maxsplit=1)[0] + line = line.split(';')[0].strip() - # Strip comments and pip environment markers - line = line.rsplit('#', maxsplit=1)[0] - line = line.split(';')[0].strip() + ops = ["==", "~=", "!=", ">", "<", ">=", "<="] - name, version = line.split('==') + if any(op in line for op in ops): + # strictly speaking, this version isn't necessarily correct, but it's + # enough for the table. + for op in ops: + if op in line: + name, version = line.split(op) elif line.startswith('-e'): rest, name = line.split('#egg=') version = rest.split('@')[1][:7] @@ -400,10 +281,19 @@ def parse_versioned_line(line): def _get_changes(diff): """Get a list of changed versions from git.""" changes_dict = {} + current_path = None + for line in diff: if not line.startswith('-') and not line.startswith('+'): continue - elif line.startswith('+++ ') or line.startswith('--- '): + elif line.startswith('--- '): + prefix = '--- a/' + current_path = pathlib.Path(line[len(prefix):]) + continue + elif line.startswith('+++ '): + prefix = '+++ b/' + new_path = pathlib.Path(line[len(prefix):]) + assert current_path == new_path, (current_path, new_path) continue elif not line.strip(): # Could be newline changes on Windows @@ -415,14 +305,14 @@ def _get_changes(diff): name, version = parse_versioned_line(line[1:]) if name not in changes_dict: - changes_dict[name] = Change(name) + changes_dict[name] = Change(name, base_path=current_path) if line.startswith('-'): changes_dict[name].old = version elif line.startswith('+'): changes_dict[name].new = version - return [change for _name, change in sorted(changes_dict.items())] + return sorted(changes_dict.values()) def print_changed_files(): @@ -449,8 +339,8 @@ def print_changed_files(): print('::set-output name=changed::' + files_text.replace('\n', '%0A')) table_header = [ - '| Requirement | old | new |', - '|-------------|-----|-----|', + '| File | Requirement | old | new |', + '|------|-------------|-----|-----|', ] diff_table = '%0A'.join(table_header + [change.table_str() for change in changes]) @@ -494,7 +384,8 @@ def build_requirements(name): init_venv(host_python=host_python, venv_dir=tmpdir, requirements=filename, - pre=comments['pre']) + pre=comments['pre'], + pip_args=comments['pip_args']) with utils.gha_group('Freezing requirements'): args = ['--all'] if name == 'tox' else [] proc = run_pip(tmpdir, 'freeze', *args, stdout=subprocess.PIPE) @@ -531,7 +422,7 @@ def test_tox(): list_proc = subprocess.run([venv_python, '-m', 'tox', '--listenvs'], check=True, stdout=subprocess.PIPE, - universal_newlines=True) + text=True) environments = list_proc.stdout.strip().split('\n') for env in environments: with utils.gha_group('tox for {}'.format(env)): @@ -553,9 +444,20 @@ def test_requirements(name, outfile, *, force=False): print(f"Skipping test as there were no changes for {name}.") return + in_file = os.path.join(REQ_DIR, 'requirements-{}.txt-raw'.format(name)) + with open(in_file, 'r', encoding='utf-8') as f: + comments = read_comments(f) + host_python = get_host_python(name) with tempfile.TemporaryDirectory() as tmpdir: - init_venv(host_python, tmpdir, outfile) + init_venv(host_python, tmpdir, outfile, pip_args=comments['pip_args']) + + +def cleanup_pylint_build(): + """Clean up pylint_checkers build files.""" + path = pathlib.Path(__file__).parent / 'pylint_checkers' / 'build' + utils.print_col(f'$ rm -r {path}', 'blue') + shutil.rmtree(path) def main(): @@ -571,6 +473,8 @@ def main(): utils.print_title(name) outfile = build_requirements(name) test_requirements(name, outfile, force=args.force_test) + if name == 'pylint': + cleanup_pylint_build() utils.print_title('Testing via tox') if args.names and not args.force_test: diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py index d0385bd17..e044de976 100644 --- a/scripts/dev/run_pylint_on_tests.py +++ b/scripts/dev/run_pylint_on_tests.py @@ -59,8 +59,13 @@ def main(): 'len-as-condition', 'compare-to-empty-string', 'pointless-statement', + 'use-implicit-booleaness-not-comparison', # directories without __init__.py... 'import-error', + # tests/helpers imports + 'wrong-import-order', + # __tracebackhide__ + 'unnecessary-lambda-assignment', ] toxinidir = sys.argv[1] diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index 82bbdb167..1267a278a 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -176,13 +176,15 @@ def _get_setting_quickref(): def _get_configtypes(): """Get configtypes classes to document.""" - predicate = lambda e: ( - inspect.isclass(e) and - # pylint: disable=protected-access - e not in [configtypes.BaseType, configtypes.MappingType, - configtypes._Numeric, configtypes.FontBase] and - # pylint: enable=protected-access - issubclass(e, configtypes.BaseType)) + def predicate(e): + return ( + inspect.isclass(e) and + # pylint: disable=protected-access + e not in [configtypes.BaseType, configtypes.MappingType, + configtypes._Numeric, configtypes.FontBase] and + # pylint: enable=protected-access + issubclass(e, configtypes.BaseType) + ) yield from inspect.getmembers(configtypes, predicate) @@ -444,7 +446,7 @@ def _generate_setting_option(f, opt): if opt.restart: f.write("\nThis setting requires a restart.\n") if opt.supports_pattern: - f.write("\nThis setting supports URL patterns.\n") + f.write("\nThis setting supports link:configuring{outfilesuffix}#patterns[URL patterns].\n") if opt.no_autoconfig: f.write("\nThis setting can only be set in config.py.\n") _generate_setting_backend_info(f, opt) @@ -536,7 +538,9 @@ def regenerate_manpage(filename): # pylint: disable=protected-access for group in parser._action_groups: groupdata = [] - groupdata.append('=== {}'.format(group.title)) + # https://bugs.python.org/issue9694 backport + title = "options" if group.title == "optional arguments" else group.title + groupdata.append('=== {}'.format(title)) if group.description is not None: groupdata.append(group.description) for action in group._group_actions: diff --git a/scripts/dev/update_3rdparty.py b/scripts/dev/update_3rdparty.py index 88f56b7f3..b1991fa1f 100755 --- a/scripts/dev/update_3rdparty.py +++ b/scripts/dev/update_3rdparty.py @@ -64,7 +64,20 @@ def download_nsis_plugins(): urllib.request.urlcleanup() -def get_latest_pdfjs_url(gh_token): +def find_pdfjs_asset(assets, legacy): + """Find the PDF.js asset to use.""" + for asset in assets: + name = asset["name"] + if ( + name.startswith("pdfjs-") and + name.endswith("-dist.zip") and + name.endswith("-legacy-dist.zip") == legacy + ): + return asset + raise Exception(f"No pdfjs found in {assets}") + + +def get_latest_pdfjs_url(gh_token, legacy): """Get the URL of the latest pdf.js prebuilt package. Returns a (version, url)-tuple. @@ -83,22 +96,25 @@ def get_latest_pdfjs_url(gh_token): with urllib.request.urlopen(request) as fp: data = json.loads(fp.read().decode('utf-8')) - download_url = data['assets'][0]['browser_download_url'] + asset = find_pdfjs_asset(data["assets"], legacy=legacy) + + download_url = asset['browser_download_url'] version_name = data['name'] return (version_name, download_url) -def update_pdfjs(target_version=None, gh_token=None): +def update_pdfjs(target_version=None, legacy=False, gh_token=None): """Download and extract the latest pdf.js version. If target_version is not None, download the given version instead. Args: target_version: None or version string ('x.y.z') + legacy: Whether to download the legacy build for 83-based. gh_token: GitHub token to use for the API. Optional except on CI. """ if target_version is None: - version, url = get_latest_pdfjs_url(gh_token) + version, url = get_latest_pdfjs_url(gh_token, legacy=legacy) else: # We need target_version as x.y.z, without the 'v' prefix, though the # user might give it on the command line @@ -107,13 +123,14 @@ def update_pdfjs(target_version=None, gh_token=None): # version should have the prefix to be consistent with the return value # of get_latest_pdfjs_url() version = 'v' + target_version + suffix = "-legacy" if legacy else "" url = ('https://github.com/mozilla/pdf.js/releases/download/' - 'v{0}/pdfjs-{0}-dist.zip').format(target_version) + f'{version}/pdfjs-{target_version}{suffix}-dist.zip') os.chdir(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..')) target_path = os.path.join('qutebrowser', '3rdparty', 'pdfjs') - print("=> Downloading pdf.js {}".format(version)) + print(f"=> Downloading pdf.js {version}{' (legacy)' if legacy else ''}") try: (archive_path, _headers) = urllib.request.urlretrieve(url) except urllib.error.HTTPError as error: @@ -160,20 +177,20 @@ def test_dicts(): print('Testing dictionary {}... '.format(lang.code), end='') lang_url = urllib.parse.urljoin(dictcli.API_URL, lang.remote_filename) request = urllib.request.Request(lang_url, method='HEAD') - response = urllib.request.urlopen(request) - if response.status == 200: - print('OK') - else: - print('ERROR: {}'.format(response.status)) + with urllib.request.urlopen(request) as response: + if response.status == 200: + print('OK') + else: + print('ERROR: {}'.format(response.status)) -def run(nsis=False, ace=False, pdfjs=True, fancy_dmg=False, pdfjs_version=None, - dicts=False, gh_token=None): +def run(nsis=False, ace=False, pdfjs=True, legacy_pdfjs=False, fancy_dmg=False, + pdfjs_version=None, dicts=False, gh_token=None): """Update components based on the given arguments.""" if nsis: download_nsis_plugins() if pdfjs: - update_pdfjs(pdfjs_version, gh_token=gh_token) + update_pdfjs(pdfjs_version, legacy=legacy_pdfjs, gh_token=gh_token) if ace: update_ace() if fancy_dmg: @@ -191,6 +208,9 @@ def main(): help='Specify pdfjs version. If not given, ' 'the latest version is used.', required=False, metavar='VERSION') + parser.add_argument("--legacy-pdfjs", + help="Use legacy PDF.js build (for 83-based)", + action='store_true') parser.add_argument('--fancy-dmg', help="Update fancy-dmg Makefile", action='store_true') parser.add_argument( @@ -202,7 +222,8 @@ def main(): '--gh-token', help="GitHub token to use.", nargs='?') args = parser.parse_args() run(nsis=False, ace=True, pdfjs=True, fancy_dmg=args.fancy_dmg, - pdfjs_version=args.pdfjs, dicts=args.dicts, gh_token=args.gh_token) + pdfjs_version=args.pdfjs, legacy_pdfjs=args.legacy_pdfjs, + dicts=args.dicts, gh_token=args.gh_token) if __name__ == '__main__': diff --git a/scripts/dictcli.py b/scripts/dictcli.py index 8cb93fb8a..a937fd31d 100755 --- a/scripts/dictcli.py +++ b/scripts/dictcli.py @@ -142,11 +142,11 @@ def parse_entry(entry): def language_list_from_api(): """Return a JSON with a list of available languages from Google API.""" listurl = API_URL + '?format=JSON' - response = urllib.request.urlopen(listurl) - # A special 5-byte prefix must be stripped from the response content - # See: https://github.com/google/gitiles/issues/22 - # https://github.com/google/gitiles/issues/82 - json_content = response.read()[5:] + with urllib.request.urlopen(listurl) as response: + # A special 5-byte prefix must be stripped from the response content + # See: https://github.com/google/gitiles/issues/22 + # https://github.com/google/gitiles/issues/82 + json_content = response.read()[5:] entries = json.loads(json_content.decode('utf-8'))['entries'] parsed_entries = [parse_entry(entry) for entry in entries] return [entry for entry in parsed_entries if entry is not None] @@ -176,8 +176,8 @@ def available_languages(): def download_dictionary(url, dest): """Download a decoded dictionary file.""" - response = urllib.request.urlopen(url) - decoded = base64.decodebytes(response.read()) + with urllib.request.urlopen(url) as response: + decoded = base64.decodebytes(response.read()) with open(dest, 'bw') as dict_file: dict_file.write(decoded) diff --git a/scripts/hostblock_blame.py b/scripts/hostblock_blame.py index 38acaa58d..b18c62925 100644 --- a/scripts/hostblock_blame.py +++ b/scripts/hostblock_blame.py @@ -41,8 +41,8 @@ def main(): for url in configdata.DATA['content.blocking.hosts.lists'].default: print("checking {}...".format(url)) - raw_file = urllib.request.urlopen(url) - byte_io = io.BytesIO(raw_file.read()) + with urllib.request.urlopen(url) as raw_file: + byte_io = io.BytesIO(raw_file.read()) f = hostblock.get_fileobj(byte_io) for line in f: line = line.decode('utf-8') diff --git a/scripts/link_pyqt.py b/scripts/link_pyqt.py index 158cc145d..0ec7f2556 100644 --- a/scripts/link_pyqt.py +++ b/scripts/link_pyqt.py @@ -44,13 +44,13 @@ def run_py(executable, *code): f.write('\n'.join(code)) cmd = [executable, filename] try: - ret = subprocess.run(cmd, universal_newlines=True, check=True, + ret = subprocess.run(cmd, text=True, check=True, stdout=subprocess.PIPE).stdout finally: os.remove(filename) else: cmd = [executable, '-c', '\n'.join(code)] - ret = subprocess.run(cmd, universal_newlines=True, check=True, + ret = subprocess.run(cmd, text=True, check=True, stdout=subprocess.PIPE).stdout return ret.rstrip() diff --git a/scripts/mkvenv.py b/scripts/mkvenv.py index 7f6920bb8..737ea145d 100755 --- a/scripts/mkvenv.py +++ b/scripts/mkvenv.py @@ -147,7 +147,7 @@ def run_venv( return subprocess.run( [str(venv_dir / subdir / executable)] + [str(arg) for arg in args], check=True, - universal_newlines=capture_output or capture_error, + text=capture_output or capture_error, stdout=subprocess.PIPE if capture_output else None, stderr=subprocess.PIPE if capture_error else None, env=proc_env, diff --git a/scripts/opengl_info.py b/scripts/opengl_info.py new file mode 100644 index 000000000..1bfdf22aa --- /dev/null +++ b/scripts/opengl_info.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2020-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org> + +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. + +"""Show information about the OpenGL setup.""" + +from PyQt5.QtGui import (QOpenGLContext, QOpenGLVersionProfile, + QOffscreenSurface, QGuiApplication) + +app = QGuiApplication([]) + +surface = QOffscreenSurface() +surface.create() + +ctx = QOpenGLContext() +ok = ctx.create() +assert ok + +ok = ctx.makeCurrent(surface) +assert ok + +print(f"GLES: {ctx.isOpenGLES()}") + +vp = QOpenGLVersionProfile() +vp.setVersion(2, 0) + +vf = ctx.versionFunctions(vp) +print(f"Vendor: {vf.glGetString(vf.GL_VENDOR)}") +print(f"Renderer: {vf.glGetString(vf.GL_RENDERER)}") +print(f"Version: {vf.glGetString(vf.GL_VERSION)}") +print(f"Shading language version: {vf.glGetString(vf.GL_SHADING_LANGUAGE_VERSION)}") + +ctx.doneCurrent() diff --git a/scripts/setupcommon.py b/scripts/setupcommon.py index b1d246044..bd549d7cc 100644 --- a/scripts/setupcommon.py +++ b/scripts/setupcommon.py @@ -43,7 +43,7 @@ def _call_git(gitpath, *args): return subprocess.run( ['git'] + list(args), cwd=gitpath, check=True, - stdout=subprocess.PIPE).stdout.decode('UTF-8').strip() + stdout=subprocess.PIPE, text=True).stdout.strip() def _git_str(): @@ -72,9 +72,8 @@ try: ['qutebrowser = qutebrowser.qutebrowser:main']}, zip_safe=True, install_requires=['jinja2', 'PyYAML', - 'dataclasses; python_version < "3.7"', 'importlib_resources>=1.1.0; python_version < "3.9"'], - python_requires='>=3.6', + python_requires='>=3.7', name='qutebrowser', version=_get_constant('version'), description=_get_constant('description'), @@ -96,7 +95,6 @@ try: 'Operating System :: MacOS', 'Operating System :: POSIX :: BSD', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', diff --git a/tests/conftest.py b/tests/conftest.py index 40631af34..f7e685f4e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,31 +17,29 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. -# pylint: disable=unused-import,wildcard-import,unused-wildcard-import - """The qutebrowser test suite conftest file.""" import os import pathlib import sys -import warnings import pytest import hypothesis -from PyQt5.QtCore import PYQT_VERSION pytest.register_assert_rewrite('helpers') +# pylint: disable=wildcard-import,unused-import,unused-wildcard-import from helpers import logfail from helpers.logfail import fail_on_logging from helpers.messagemock import message_mock from helpers.fixtures import * # noqa: F403 +# pylint: enable=wildcard-import,unused-import,unused-wildcard-import from helpers import testutils -from qutebrowser.utils import qtutils, standarddir, usertypes, utils, version +from qutebrowser.utils import usertypes, utils, version from qutebrowser.misc import objects, earlyinit -from qutebrowser.qt import sip -import qutebrowser.app # To register commands +# To register commands +import qutebrowser.app # pylint: disable=unused-import _qute_scheme_handler = None @@ -196,9 +194,8 @@ def pytest_ignore_collect(path): @pytest.fixture(scope='session') def qapp_args(): """Make QtWebEngine unit tests run on older Qt versions + newer kernels.""" - seccomp_args = testutils.seccomp_args(qt_flag=False) - if seccomp_args: - return [sys.argv[0]] + seccomp_args + if testutils.disable_seccomp_bpf_sandbox(): + return [sys.argv[0], testutils.DISABLE_SECCOMP_BPF_FLAG] return [] @@ -214,20 +211,76 @@ def pytest_addoption(parser): help="Delay between qutebrowser commands.") parser.addoption('--qute-profile-subprocs', action='store_true', default=False, help="Run cProfile for subprocesses.") - parser.addoption('--qute-bdd-webengine', action='store_true', - help='Use QtWebEngine for BDD tests') + parser.addoption('--qute-backend', action='store', + choices=['webkit', 'webengine'], help='Set backend for BDD tests') def pytest_configure(config): - webengine_arg = config.getoption('--qute-bdd-webengine') - webengine_env = os.environ.get('QUTE_BDD_WEBENGINE', 'false') - config.webengine = webengine_arg or webengine_env == 'true' - # Fail early if QtWebEngine is not available - if config.webengine: - import PyQt5.QtWebEngineWidgets + backend = _select_backend(config) + config.webengine = backend == 'webengine' + earlyinit.configure_pyqt() +def _select_backend(config): + """Select the backend for running tests. + + The backend is auto-selected in the following manner: + 1. Use QtWebKit if available + 2. Otherwise use QtWebEngine as a fallback + + Auto-selection is overridden by either passing a backend via + `--qute-backend=<backend>` or setting the environment variable + `QUTE_TESTS_BACKEND=<backend>`. + + Args: + config: pytest config + + Raises: + ImportError if the selected backend is not available. + + Returns: + The selected backend as a string (e.g. 'webkit'). + """ + backend_arg = config.getoption('--qute-backend') + backend_env = os.environ.get('QUTE_TESTS_BACKEND') + + backend = backend_arg or backend_env or _auto_select_backend() + + # Fail early if selected backend is not available + # pylint: disable=unused-import + if backend == 'webkit': + import PyQt5.QtWebKitWidgets + elif backend == 'webengine': + import PyQt5.QtWebEngineWidgets + else: + raise utils.Unreachable(backend) + + return backend + + +def _auto_select_backend(): + # pylint: disable=unused-import + try: + # Try to use QtWebKit as the default backend + import PyQt5.QtWebKitWidgets + return 'webkit' + except ImportError: + # Try to use QtWebEngine as a fallback and fail early + # if that's also not available + import PyQt5.QtWebEngineWidgets + return 'webengine' + + +def pytest_report_header(config): + if config.webengine: + backend_version = version.qtwebengine_versions(avoid_init=True) + else: + backend_version = version.qWebKitVersion() + + return f'backend: {backend_version}' + + @pytest.fixture(scope='session', autouse=True) def check_display(request): if utils.is_linux and not os.environ.get('DISPLAY', ''): @@ -282,10 +335,10 @@ def check_yaml_c_exts(): """Make sure PyYAML C extensions are available on CI. Not available yet with a nightly Python, see: - https://github.com/yaml/pyyaml/issues/416 + https://github.com/yaml/pyyaml/issues/630 """ - if testutils.ON_CI and sys.version_info[:2] != (3, 10): - from yaml import CLoader + if testutils.ON_CI and sys.version_info[:2] != (3, 11): + from yaml import CLoader # pylint: disable=unused-import @pytest.hookimpl(tryfirst=True, hookwrapper=True) diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index a4a089cea..5241ab4c3 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -17,12 +17,9 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. -# pylint: disable=unused-import - """Things needed for end2end testing.""" import re -import os import pathlib import sys import shutil @@ -34,13 +31,14 @@ from PyQt5.QtCore import PYQT_VERSION, QCoreApplication pytest.register_assert_rewrite('end2end.fixtures') +# pylint: disable=unused-import from end2end.fixtures.notificationserver import notification_server from end2end.fixtures.webserver import server, server_per_test, server2, ssl_server from end2end.fixtures.quteprocess import (quteproc_process, quteproc, quteproc_new) from end2end.fixtures.testprocess import pytest_runtest_makereport +# pylint: enable=unused-import from qutebrowser.utils import qtutils, utils -from qutebrowser.browser.webengine import spell def pytest_configure(config): @@ -57,11 +55,11 @@ def pytest_unconfigure(config): if config.getoption('--qute-profile-subprocs'): stats = pstats.Stats() for fn in pathlib.Path('prof').iterdir(): - stats.add((pathlib.Path('prof') / fn)) - stats.dump_stats((pathlib.Path('prof') / 'combined.pstats')) + stats.add(pathlib.Path('prof') / fn) + stats.dump_stats(pathlib.Path('prof') / 'combined.pstats') -def _check_hex_version(op_str, running_version, version): +def _check_version(op_str, running_version, version_str, as_hex=False): operators = { '==': operator.eq, '!=': operator.ne, @@ -71,9 +69,12 @@ def _check_hex_version(op_str, running_version, version): '<': operator.lt, } op = operators[op_str] - major, minor, patch = [int(e) for e in version.split('.')] - hex_version = (major << 16) | (minor << 8) | patch - return op(running_version, hex_version) + major, minor, patch = [int(e) for e in version_str.split('.')] + if as_hex: + version = (major << 16) | (minor << 8) | patch + else: + version = (major, minor, patch) + return op(running_version, version) def _get_version_tag(tag): @@ -84,7 +85,7 @@ def _get_version_tag(tag): casesinto an appropriate @pytest.mark.skip marker, and falls back to """ version_re = re.compile(r""" - (?P<package>qt|pyqt|pyqtwebengine) + (?P<package>qt|pyqt|pyqtwebengine|python) (?P<operator>==|>=|!=|<) (?P<version>\d+\.\d+(\.\d+)?) """, re.VERBOSE) @@ -108,10 +109,11 @@ def _get_version_tag(tag): return pytest.mark.skipif(do_skip[op], reason='Needs ' + tag) elif package == 'pyqt': return pytest.mark.skipif( - not _check_hex_version( + not _check_version( op_str=match.group('operator'), running_version=PYQT_VERSION, - version=version + version_str=version, + as_hex=True, ), reason='Needs ' + tag, ) @@ -123,10 +125,21 @@ def _get_version_tag(tag): else: running_version = PYQT_WEBENGINE_VERSION return pytest.mark.skipif( - not _check_hex_version( + not _check_version( + op_str=match.group('operator'), + running_version=running_version, + version_str=version, + as_hex=True, + ), + reason='Needs ' + tag, + ) + elif package == 'python': + running_version = sys.version_info + return pytest.mark.skipif( + not _check_version( op_str=match.group('operator'), running_version=running_version, - version=version + version_str=version, ), reason='Needs ' + tag, ) @@ -165,7 +178,7 @@ if not getattr(sys, 'frozen', False): def pytest_collection_modifyitems(config, items): - """Apply @qtwebengine_* markers; skip unittests with QUTE_BDD_WEBENGINE.""" + """Apply @qtwebengine_* markers.""" # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884 # (note this isn't actually fixed properly before Qt 5.15) header_bug_fixed = qtutils.version_check('5.15', compiled=False) diff --git a/tests/end2end/data/brave-adblock/generate.py b/tests/end2end/data/brave-adblock/generate.py index 7a23a21ab..393cda4e7 100644 --- a/tests/end2end/data/brave-adblock/generate.py +++ b/tests/end2end/data/brave-adblock/generate.py @@ -53,9 +53,9 @@ def main(): else: request = urllib.request.Request(URL) print(f"Downloading {URL} ...") - response = urllib.request.urlopen(request) - assert response.status == 200 - data_str = response.read().decode("utf-8") + with urllib.request.urlopen(request) as response: + assert response.status == 200 + data_str = response.read().decode("utf-8") print(f"Saving to cache file {CACHE_PATH} ...") CACHE_PATH.write_text(data_str, encoding="utf-8") data = io.StringIO(data_str) diff --git a/tests/end2end/data/click_element.html b/tests/end2end/data/click_element.html index acf0cf77c..b2a691e08 100644 --- a/tests/end2end/data/click_element.html +++ b/tests/end2end/data/click_element.html @@ -6,9 +6,11 @@ <span id='test' onclick='console.log("click_element clicked")'>Test Element</span> <span onclick='console.log("click_element special chars")'>"Don't", he shouted</span> <span>Duplicate</span> - <span>Duplicate</span> - <form><input id='qute-input'></input></form> + <span class='clickable' onclick='console.log("click_element CSS selector")'>Duplicate</span> + <form><input autofocus id='qute-input'></input></form> <a href="/data/hello.txt" id='link'>link</a> <span id='foo.bar' onclick='console.log("id with dot")'>ID with dot</span> + <span style='position: absolute; left: 20px;top: 42px; width:10px; height:10px;' + onclick='console.log("click_element position")'></span> </body> </html> diff --git a/tests/end2end/data/darkmode/mathml-display.html b/tests/end2end/data/darkmode/mathml-display.html new file mode 100644 index 000000000..f0c280cca --- /dev/null +++ b/tests/end2end/data/darkmode/mathml-display.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>MathML-like SVG</title> + </head> + <body style="margin: 0; background-color: #ffff99"> + <!-- + Image based on: https://en.wikipedia.org/wiki/Pythagorean_theorem + with a black square added for testing. + + onload based on: + https://stackoverflow.com/questions/53423742/waiting-for-an-image-to-finish-rendering + --> + <img + class="mwe-math-fallback-image-display" + src="mathml.svg" + alt="Pythagorean theorem" + onload="requestAnimationFrame(() => requestAnimationFrame(() => console.log('Image loaded')));" + > + <!-- --> + </body> +</html> diff --git a/tests/end2end/data/darkmode/mathml.html b/tests/end2end/data/darkmode/mathml-inline.html index fa2371638..fa2371638 100644 --- a/tests/end2end/data/darkmode/mathml.html +++ b/tests/end2end/data/darkmode/mathml-inline.html diff --git a/tests/end2end/data/hints/html/README.md b/tests/end2end/data/hints/html/README.md index 2a6e97c24..5bbaecb4a 100644 --- a/tests/end2end/data/hints/html/README.md +++ b/tests/end2end/data/hints/html/README.md @@ -3,3 +3,5 @@ Tests in this directory are automatically picked up by `test_hints` in They need to contain a special `<!-- target: foo.html -->` comment which specifies where the hint in it will point to, and will then test that. + +With `<!-- target: null -->`, the page is expected to not generate any hints. diff --git a/tests/end2end/data/hints/invisible.html b/tests/end2end/data/hints/html/invisible.html index b0bfa9dd9..d382c80fa 100644 --- a/tests/end2end/data/hints/invisible.html +++ b/tests/end2end/data/hints/html/invisible.html @@ -1,3 +1,5 @@ +<!-- target: null --> + <!DOCTYPE html> <html> diff --git a/tests/end2end/data/hints/html/tabindex-negative.html b/tests/end2end/data/hints/html/tabindex-negative.html new file mode 100644 index 000000000..03adb32bf --- /dev/null +++ b/tests/end2end/data/hints/html/tabindex-negative.html @@ -0,0 +1,13 @@ +<!-- target: null --> + +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Span with tabindex -1</title> + </head> + <body> + <p>This text should not get a hint:</p> + <span tabindex=-1>test</span> + </body> +</html> diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index cf35c5356..47153b741 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -241,11 +241,6 @@ Feature: Using hints # The actual check is already done above Then "No elements found." should not be logged - Scenario: Hinting invisible elements - When I open data/hints/invisible.html - And I run :hint - Then the error "No elements found." should be shown - Scenario: Clicking input with existing text When I open data/hints/input.html And I run :click-element id qute-input-existing diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index e6a02e038..26fe8f357 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -389,7 +389,7 @@ Feature: Various utility commands. Scenario: Partial commandline matching with startup command When I run :message-i "Hello World" (invalid command) - Then the error "message-i: no such command" should be shown + Then the error "message-i: no such command (did you mean :message-info?)" should be shown Scenario: Multiple leading : in command When I run :::::set-cmd-text ::::message-i "Hello World" @@ -436,7 +436,7 @@ Feature: Various utility commands. Scenario: Clicking an element with unknown ID When I open data/click_element.html And I run :click-element id blah - Then the error "No element found with id blah!" should be shown + Then the error "No element found with ID "blah"!" should be shown Scenario: Clicking an element by ID When I open data/click_element.html @@ -457,6 +457,49 @@ Feature: Various utility commands. - data/click_element.html - data/hello.txt (active) + Scenario: Clicking an element by CSS selector + When I open data/click_element.html + And I run :click-element css .clickable + Then the javascript message "click_element CSS selector" should be logged + + Scenario: Clicking an element with non-unique filter + When I open data/click_element.html + And I run :click-element css span + Then the error "Multiple elements found matching CSS selector "span"!" should be shown + + Scenario: Clicking first element matching a selector + When I open data/click_element.html + And I run :click-element --select-first css span + Then the javascript message "click_element clicked" should be logged + + Scenario: Clicking an element by position + When I open data/click_element.html + And I run :click-element position 20,42 + Then the javascript message "click_element position" should be logged + + Scenario: Clicking an element with invalid position + When I open data/click_element.html + And I run :click-element position 20.42 + Then the error "String 20.42 does not match X,Y" should be shown + + Scenario: Clicking an element with non-integer position + When I open data/click_element.html + And I run :click-element position 20,42.001 + Then the error "String 20,42.001 does not match X,Y" should be shown + + Scenario: Clicking on focused element when there is none + When I open data/click_element.html + # Need to loose focus on input element + And I run :click-element position 20,42 + And I wait for the javascript message "click_element position" + And I run :click-element focused + Then the error "No element found with focus!" should be shown + + Scenario: Clicking on focused element + When I open data/click_element.html + And I run :click-element focused + Then "Entering mode KeyMode.insert (reason: clicking input)" should be logged + ## :command-history-{prev,next} Scenario: Calling previous command diff --git a/tests/end2end/features/notifications.feature b/tests/end2end/features/notifications.feature index 08c5f725f..cd34793ef 100644 --- a/tests/end2end/features/notifications.feature +++ b/tests/end2end/features/notifications.feature @@ -4,7 +4,8 @@ Feature: Notifications HTML5 notification API interaction Background: - Given I open data/javascript/notifications.html + Given I clean up open tabs + And I open data/javascript/notifications.html And I set content.notifications.enabled to true And I run :click-element id button And I clean up the notification server @@ -120,8 +121,13 @@ Feature: Notifications Scenario: User clicks presented notification When I run :click-element id show-button And I wait for the javascript message "notification shown" + And I open about:blank in a new tab And I click the notification Then the javascript message "notification clicked" should be logged + And the following tabs should be open: + - about:blank + - data/javascript/notifications.html (active) + - about:blank @pyqtwebengine<5.15.0 Scenario: User clicks presented notification (old Qt) diff --git a/tests/end2end/features/search.feature b/tests/end2end/features/search.feature index 5fafd19f0..305b45690 100644 --- a/tests/end2end/features/search.feature +++ b/tests/end2end/features/search.feature @@ -71,7 +71,25 @@ Feature: Searching on a page When I run :search foo And I wait for "search found foo" in the log And I run :search foo - Then "Ignoring duplicate search request for foo" should be logged + Then "Ignoring duplicate search request for foo, but resetting flags" should be logged + + Scenario: Reset search direction on duplicate search, forward-to-back + When I run :search baz + And I wait for "search found baz" in the log + And I run :search -r baz + And I wait for "Ignoring duplicate search request for baz, but resetting flags" in the log + And I run :search-next + And I wait for "next_result found baz with flags FindBackward" in the log + Then "BAZ" should be found + + Scenario: Reset search direction on duplicate search, back-to-forward + When I run :search -r baz + And I wait for "search found baz with flags FindBackward" in the log + And I run :search baz + And I wait for "Ignoring duplicate search request for baz, but resetting flags" in the log + And I run :search-next + And I wait for "next_result found baz" in the log + Then "baz" should be found ## search.ignore_case diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index e8fc9e7bc..54e84a10e 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -633,6 +633,27 @@ Feature: Tab management - data/numbers/1.txt (active) - data/numbers/3.txt + Scenario: :tab-move with absolute position + When I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I open data/numbers/3.txt in a new tab + And I run :tab-focus 1 + And I run :tab-move end + Then the following tabs should be open: + - data/numbers/2.txt + - data/numbers/3.txt + - data/numbers/1.txt (active) + + Scenario: :tab-move with absolute position + When I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I open data/numbers/3.txt in a new tab + And I run :tab-move start + Then the following tabs should be open: + - data/numbers/3.txt (active) + - data/numbers/1.txt + - data/numbers/2.txt + Scenario: Make sure :tab-move retains metadata When I open data/title.html And I open data/hello.txt in a new tab @@ -653,7 +674,7 @@ Feature: Tab management Scenario: :tab-clone with -b and -w When I run :tab-clone -b -w - Then the error "Only one of -b/-w can be given!" should be shown. + Then the error "Only one of -b/-w/-p can be given!" should be shown. Scenario: Cloning a tab with history and title When I open data/title.html @@ -737,6 +758,26 @@ Feature: Tab management - url: http://localhost:*/data/title.html title: Test title + Scenario: Cloning to private window + When I open data/title.html + And I run :tab-clone -p + And I wait until data/title.html is loaded + Then the session should look like: + windows: + - tabs: + - active: true + history: + - url: about:blank + - url: http://localhost:*/data/title.html + title: Test title + - private: true + tabs: + - active: true + history: + - url: about:blank + - url: http://localhost:*/data/title.html + title: Test title + # https://github.com/qutebrowser/qutebrowser/issues/2289 @qtwebkit_skip @@ -1329,6 +1370,25 @@ Feature: Tab management And I run :tab-take 0/1 Then the error "Can't take tabs when using windows as tabs" should be shown + @windows_skip + Scenario: Close the last tab of a window when taken by another window + Given I have a fresh instance + When I open data/numbers/1.txt + And I run :tab-only + And I open data/numbers/2.txt in a new window + And I set tabs.last_close to ignore + And I run :tab-take 1/1 + And I wait until data/numbers/2.txt is loaded + Then the session should look like: + windows: + - tabs: + - history: + - url: about:blank + - url: http://localhost:*/data/numbers/1.txt + - active: true + history: + - url: http://localhost:*/data/numbers/2.txt + # :tab-give @xfail_norun # Needs qutewm @@ -1386,6 +1446,24 @@ Feature: Tab management And I run :tab-give 0 Then the error "Can't give tabs when using windows as tabs" should be shown + @windows_skip + Scenario: Close the last tab of a window when given to another window + Given I have a fresh instance + When I open data/numbers/1.txt + And I run :tab-only + And I open data/numbers/2.txt in a new window + And I set tabs.last_close to ignore + And I run :tab-give 1 + And I wait until data/numbers/1.txt is loaded + Then the session should look like: + windows: + - tabs: + - active: true + history: + - url: http://localhost:*/data/numbers/2.txt + - history: + - url: http://localhost:*/data/numbers/1.txt + # Other Scenario: Using :tab-next after closing last tab (#1448) @@ -1663,3 +1741,22 @@ Feature: Tab management And I run :undo And I run :message-info "Still alive!" Then the message "Still alive!" should be shown + + Scenario: Passthrough mode override + When I run :set -u localhost:*/data/numbers/1.txt input.mode_override 'passthrough' + And I open data/numbers/1.txt + Then "Entering mode KeyMode.passthrough (reason: mode_override)" should be logged + + Scenario: Insert mode override + When I run :set -u localhost:*/data/numbers/1.txt input.mode_override 'insert' + And I open data/numbers/1.txt + Then "Entering mode KeyMode.insert (reason: mode_override)" should be logged + + Scenario: Mode override on tab switch + When I run :set -u localhost:*/data/numbers/1.txt input.mode_override 'insert' + And I open data/numbers/1.txt + And I wait for "Entering mode KeyMode.insert (reason: mode_override)" in the log + And I run :fake-key -g <esc> + And I open data/numbers/2.txt in a new tab + And I run :tab-prev + Then "Entering mode KeyMode.insert (reason: mode_override)" should be logged diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index bb2b3e2fb..e8172ae20 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -93,6 +93,7 @@ Feature: Miscellaneous utility commands exposed to the user. ## :debug-cache-stats + @python>=3.9.0 Scenario: :debug-cache-stats When I run :debug-cache-stats Then "is_valid_prefix: CacheInfo(*)" should be logged diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 3cbea01ad..ab8f28d26 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -48,10 +48,7 @@ instance_counter = itertools.count() def is_ignored_qt_message(pytestconfig, message): """Check if the message is listed in qt_log_ignore.""" regexes = pytestconfig.getini('qt_log_ignore') - for regex in regexes: - if re.search(regex, message): - return True - return False + return any(re.search(regex, message) for regex in regexes) def is_ignored_lowlevel_message(message): @@ -552,8 +549,8 @@ class QuteProc(testprocess.Process): '--debug-flag', 'werror', '--debug-flag', 'test-notification-service'] - if self.request.config.webengine: - args += testutils.seccomp_args(qt_flag=True) + if self.request.config.webengine and testutils.disable_seccomp_bpf_sandbox(): + args += testutils.DISABLE_SECCOMP_BPF_ARGS args.append('about:blank') return args @@ -609,8 +606,7 @@ class QuteProc(testprocess.Process): self.wait_for(category='webview', message='Scroll position changed to ' + point) - def wait_for(self, timeout=None, # pylint: disable=arguments-differ - **kwargs): + def wait_for(self, timeout=None, **kwargs): """Extend wait_for to add divisor if a test is xfailing.""" __tracebackhide__ = (lambda e: e.errisinstance(testprocess.WaitForTimeout)) @@ -716,7 +712,7 @@ class QuteProc(testprocess.Process): target_arg) self._wait_for_ipc() - def start(self, *args, **kwargs): # pylint: disable=arguments-differ + def start(self, *args, **kwargs): try: super().start(*args, **kwargs) except testprocess.ProcessExited: @@ -913,8 +909,8 @@ class QuteProc(testprocess.Process): """Get a screenshot of the current page. Arguments: - probe: If given, only continue if the pixel at the given position isn't - black (or whatever is specified by probe_color). + probe_pos: If given, only continue if the pixel at the given + position isn't black (or whatever is specified by probe_color). """ for _ in range(5): tmp_path = self.request.getfixturevalue('tmp_path') diff --git a/tests/end2end/fixtures/test_webserver.py b/tests/end2end/fixtures/test_webserver.py index 3c825e5bc..ed0c32ae5 100644 --- a/tests/end2end/fixtures/test_webserver.py +++ b/tests/end2end/fixtures/test_webserver.py @@ -37,7 +37,8 @@ def test_server(server, qtbot, path, content, expected): with qtbot.wait_signal(server.new_request, timeout=100): url = 'http://localhost:{}{}'.format(server.port, path) try: - response = urllib.request.urlopen(url) + with urllib.request.urlopen(url) as response: + data = response.read().decode('utf-8') except urllib.error.HTTPError as e: # "Though being an exception (a subclass of URLError), an HTTPError # can also function as a non-exceptional file-like return value @@ -46,8 +47,6 @@ def test_server(server, qtbot, path, content, expected): print(e.read().decode('utf-8')) raise - data = response.read().decode('utf-8') - assert server.get_requests() == [server.ExpectedRequest('GET', path)] assert (content in data) == expected diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py index 0fc32cd88..2c2eab930 100644 --- a/tests/end2end/fixtures/webserver.py +++ b/tests/end2end/fixtures/webserver.py @@ -149,11 +149,9 @@ class WebserverProcess(testprocess.Process): def _random_port(self) -> int: """Get a random free port.""" - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.bind(('localhost', 0)) - port = sock.getsockname()[1] - sock.close() - return port + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind(('localhost', 0)) + return sock.getsockname()[1] def get_requests(self): """Get the requests to the server during this test.""" @@ -162,7 +160,7 @@ class WebserverProcess(testprocess.Process): def _parse_line(self, line): self._log(line) - started_re = re.compile(r' \* Running on https?://127\.0\.0\.1:{}/ ' + started_re = re.compile(r' \* Running on https?://127\.0\.0\.1:{}/? ' r'\(Press CTRL\+C to quit\)'.format(self.port)) if started_re.fullmatch(line): self.ready.emit() diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index 92f3091d1..392fbe43f 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -231,7 +231,7 @@ def drip(): def generate_bytes(): for _ in range(numbytes): - yield "*".encode('utf-8') + yield b"*" time.sleep(pause) response = flask.Response(generate_bytes(), headers={ @@ -292,7 +292,9 @@ def view_user_agent(): @app.route('/favicon.ico') def favicon(): - icon_dir = END2END_DIR.parents[1] / 'icons' + # WORKAROUND for https://github.com/PyCQA/pylint/issues/5783 + # pylint: disable-next=no-member,useless-suppression + icon_dir = END2END_DIR.parents[1] / 'qutebrowser' / 'icons' return flask.send_from_directory( icon_dir, 'qutebrowser.ico', mimetype='image/vnd.microsoft.icon') diff --git a/tests/end2end/test_hints_html.py b/tests/end2end/test_hints_html.py index ebb2a7e33..5af09649f 100644 --- a/tests/end2end/test_hints_html.py +++ b/tests/end2end/test_hints_html.py @@ -40,7 +40,7 @@ def collect_tests(): @dataclasses.dataclass class ParsedFile: - target: str + target: Optional[str] qtwebengine_todo: Optional[str] @@ -59,7 +59,7 @@ def _parse_file(test_name): with file_path.open('r', encoding='utf-8') as html: soup = bs4.BeautifulSoup(html, 'html.parser') - comment = str(soup.find(text=lambda text: isinstance(text, bs4.Comment))) + comment = str(soup.find(string=lambda text: isinstance(text, bs4.Comment))) if comment is None: raise InvalidFile(test_name, "no comment found") @@ -107,11 +107,18 @@ def test_hints(test_name, zoom_text_only, zoom_level, find_implementation, quteproc.set_setting('zoom.text_only', str(zoom_text_only)) quteproc.set_setting('hints.find_implementation', find_implementation) quteproc.send_cmd(':zoom {}'.format(zoom_level)) + # follow hint quteproc.send_cmd(':hint all normal') - quteproc.wait_for(message='hints: a', category='hints') - quteproc.send_cmd(':hint-follow a') - quteproc.wait_for_load_finished('data/' + parsed.target) + + if parsed.target is None: + msg = quteproc.wait_for(message='No elements found.', category='message') + msg.expected = True + else: + quteproc.wait_for(message='hints: a', category='hints') + quteproc.send_cmd(':hint-follow a') + quteproc.wait_for_load_finished('data/' + parsed.target) + # reset quteproc.send_cmd(':zoom 100') if not request.config.webengine: diff --git a/tests/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py index e829f25a6..47e481b3d 100644 --- a/tests/end2end/test_insert_mode.py +++ b/tests/end2end/test_insert_mode.py @@ -22,7 +22,7 @@ import pytest -@pytest.mark.parametrize(['file_name', 'elem_id', 'source', 'input_text'], [ +@pytest.mark.parametrize('file_name, elem_id, source, input_text', [ ('textarea.html', 'qute-textarea', 'clipboard', 'qutebrowser'), ('textarea.html', 'qute-textarea', 'keypress', 'superqutebrowser'), ('input.html', 'qute-input', 'clipboard', 'amazingqutebrowser'), diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index b860feed0..5a34d0357 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -56,8 +56,8 @@ def _base_args(config): else: args += ['--backend', 'webkit'] - if config.webengine: - args += testutils.seccomp_args(qt_flag=True) + if config.webengine and testutils.disable_seccomp_bpf_sandbox(): + args += testutils.DISABLE_SECCOMP_BPF_ARGS args.append('about:blank') return args @@ -336,7 +336,7 @@ def test_command_on_start(request, quteproc_new): quteproc_new.wait_for_quit() -@pytest.mark.parametrize('python', ['python2', 'python3.5']) +@pytest.mark.parametrize('python', ['python2', 'python3.6']) def test_launching_with_old_python(python): try: proc = subprocess.run( @@ -346,7 +346,7 @@ def test_launching_with_old_python(python): except FileNotFoundError: pytest.skip(f"{python} not found") assert proc.returncode == 1 - error = "At least Python 3.6.1 is required to run qutebrowser" + error = "At least Python 3.7 is required to run qutebrowser" assert proc.stderr.decode('ascii').startswith(error) @@ -723,7 +723,8 @@ def test_dark_mode(webengine_versions, quteproc_new, request, ) -def test_dark_mode_mathml(quteproc_new, request, qtbot): +@pytest.mark.parametrize("suffix", ["inline", "display"]) +def test_dark_mode_mathml(quteproc_new, request, qtbot, suffix): if not request.config.webengine: pytest.skip("Skipped with QtWebKit") @@ -734,7 +735,7 @@ def test_dark_mode_mathml(quteproc_new, request, qtbot): ] quteproc_new.start(args) - quteproc_new.open_path('data/darkmode/mathml.html') + quteproc_new.open_path(f'data/darkmode/mathml-{suffix}.html') quteproc_new.wait_for_js('Image loaded') # First make sure loading finished by looking outside of the image @@ -826,3 +827,81 @@ def test_json_logging_without_debug(request, quteproc_new, runtime_tmpdir): quteproc_new.exit_expected = True quteproc_new.start(args, env={'XDG_RUNTIME_DIR': str(runtime_tmpdir)}) assert not quteproc_new.is_running() + + +@pytest.mark.qtwebkit_skip +@pytest.mark.parametrize( + 'sandboxing, has_namespaces, has_seccomp, has_yama, expected_result', [ + ('enable-all', True, True, True, "You are adequately sandboxed."), + ('disable-seccomp-bpf', True, False, True, "You are NOT adequately sandboxed."), + ('disable-all', False, False, False, "You are NOT adequately sandboxed."), + ] +) +def test_sandboxing( + request, quteproc_new, sandboxing, + has_namespaces, has_seccomp, has_yama, expected_result, +): + if not request.config.webengine: + pytest.skip("Skipped with QtWebKit") + elif sandboxing == "enable-all" and testutils.disable_seccomp_bpf_sandbox(): + pytest.skip("Full sandboxing not supported") + + args = _base_args(request.config) + [ + '--temp-basedir', + '-s', 'qt.chromium.sandboxing', sandboxing, + ] + quteproc_new.start(args) + + quteproc_new.open_url('chrome://sandbox') + text = quteproc_new.get_content() + print(text) + + not_found_msg = ("The webpage at chrome://sandbox/ might be temporarily down or " + "it may have moved permanently to a new web address.") + if not_found_msg in text.split("\n"): + line = quteproc_new.wait_for(message='Load error: ERR_INVALID_URL') + line.expected = True + pytest.skip("chrome://sandbox/ not supported") + + bpf_text = "Seccomp-BPF sandbox" + yama_text = "Ptrace Protection with Yama LSM" + + if "\n\n\n" in text: + # Qt 5.12 + header, rest = text.split("\n", maxsplit=1) + rest, result = rest.rsplit("\n\n", maxsplit=1) + lines = rest.replace("\t\n", "\t").split("\n\n\n") + + expected_status = { + "Namespace Sandbox": "Yes" if has_namespaces else "No", + "Network namespaces": "Yes" if has_namespaces else "No", + "PID namespaces": "Yes" if has_namespaces else "No", + "SUID Sandbox": "No", + + bpf_text: "Yes" if has_seccomp else "No", + f"{bpf_text} supports TSYNC": "Yes" if has_seccomp else "No", + + "Yama LSM Enforcing": "Yes" if has_yama else "No", + } + else: + header, *lines, empty, result = text.split("\n") + assert not empty + + expected_status = { + "Layer 1 Sandbox": "Namespace" if has_namespaces else "None", + + "PID namespaces": "Yes" if has_namespaces else "No", + "Network namespaces": "Yes" if has_namespaces else "No", + + bpf_text: "Yes" if has_seccomp else "No", + f"{bpf_text} supports TSYNC": "Yes" if has_seccomp else "No", + + f"{yama_text} (Broker)": "Yes" if has_yama else "No", + f"{yama_text} (Non-broker)": "No", + } + + assert header == "Sandbox Status" + assert result == expected_result + + status = dict(line.split("\t") for line in lines) + assert status == expected_status diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index cd3778b8a..cc362290f 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -186,7 +186,7 @@ def testdata_scheme(qapp): pass @qutescheme.add_handler('testdata') - def handler(url): # pylint: disable=unused-variable + def handler(url): file_abs = os.path.abspath(os.path.dirname(__file__)) filename = os.path.join(file_abs, os.pardir, 'end2end', url.path().lstrip('/')) @@ -632,7 +632,7 @@ def redirect_webengine_data(data_tmpdir, monkeypatch): monkeypatch.setenv('HOME', str(data_tmpdir)) -@pytest.fixture() +@pytest.fixture def short_tmpdir(): """A short temporary directory for a XDG_RUNTIME_DIR.""" with tempfile.TemporaryDirectory() as tdir: diff --git a/tests/helpers/logfail.py b/tests/helpers/logfail.py index ae4ac9bc0..15d5a9253 100644 --- a/tests/helpers/logfail.py +++ b/tests/helpers/logfail.py @@ -39,8 +39,7 @@ class LogFailHandler(logging.Handler): if logger.name == 'messagemock': return - if (logger.level == record.levelno or - root_logger.level == record.levelno): + if record.levelno in (logger.level, root_logger.level): # caplog.at_level(...) was used with the level of this message, # i.e. it was expected. return diff --git a/tests/helpers/testutils.py b/tests/helpers/testutils.py index 8bb622133..c607718ab 100644 --- a/tests/helpers/testutils.py +++ b/tests/helpers/testutils.py @@ -31,14 +31,9 @@ import importlib.machinery import pytest -from PyQt5.QtCore import qVersion from PyQt5.QtGui import QColor -try: - from PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION_STR -except ImportError: - PYQT_WEBENGINE_VERSION_STR = None -from qutebrowser.utils import qtutils, log, utils +from qutebrowser.utils import qtutils, log, utils, version ON_CI = 'CI' in os.environ @@ -267,35 +262,38 @@ def easyprivacy_txt(): return _decompress_gzip_datafile("easyprivacy.txt.gz") -def seccomp_args(qt_flag): - """Get necessary flags to disable the seccomp BPF sandbox. +DISABLE_SECCOMP_BPF_FLAG = "--disable-seccomp-filter-sandbox" +DISABLE_SECCOMP_BPF_ARGS = ["-s", "qt.chromium.sandboxing", "disable-seccomp-bpf"] + + +def disable_seccomp_bpf_sandbox(): + """Check whether we need to disable the seccomp BPF sandbox. This is needed for some QtWebEngine setups, with older Qt versions but newer kernels. - - Args: - qt_flag: Add a '--qt-flag' argument. """ + try: + from PyQt5 import QtWebEngine # pylint: disable=unused-import + except ImportError: + # no QtWebEngine available + return False + affected_versions = set() for base, patch_range in [ - # 5.12.0 to 5.12.7 (inclusive) - ('5.12', range(0, 8)), + # 5.12.0 to 5.12.10 (inclusive) + ('5.12', range(0, 11)), # 5.13.0 to 5.13.2 (inclusive) ('5.13', range(0, 3)), # 5.14.0 ('5.14', [0]), + # 5.15.0 to 5.15.2 (inclusive) + ('5.15', range(0, 3)), ]: for patch in patch_range: - affected_versions.add('{}.{}'.format(base, patch)) - - version = (PYQT_WEBENGINE_VERSION_STR - if PYQT_WEBENGINE_VERSION_STR is not None - else qVersion()) - if version in affected_versions: - disable_arg = 'disable-seccomp-filter-sandbox' - return ['--qt-flag', disable_arg] if qt_flag else ['--' + disable_arg] + affected_versions.add(utils.VersionNumber.parse(f'{base}.{patch}')) - return [] + versions = version.qtwebengine_versions(avoid_init=True) + return versions.webengine in affected_versions def import_userscript(name): diff --git a/tests/unit/api/test_cmdutils.py b/tests/unit/api/test_cmdutils.py index 77e648eed..b6650078c 100644 --- a/tests/unit/api/test_cmdutils.py +++ b/tests/unit/api/test_cmdutils.py @@ -17,8 +17,6 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. -# pylint: disable=unused-variable - """Tests for qutebrowser.api.cmdutils.""" import sys @@ -292,7 +290,6 @@ class TestRegister: class Enum(enum.Enum): - # pylint: disable=invalid-name x = enum.auto() y = enum.auto() diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py index 288471ea0..86014040d 100644 --- a/tests/unit/browser/test_caret.py +++ b/tests/unit/browser/test_caret.py @@ -70,7 +70,7 @@ class Selection: self._qtbot.wait(50) - assert False, 'Failed to get selection!' + pytest.fail('Failed to get selection!') def check_multiline(self, expected, *, strip=False): self.check(textwrap.dedent(expected).strip(), strip=strip) diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index 7906d385c..51a9effb8 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -141,7 +141,7 @@ class TestDelete: class TestAdd: - @pytest.fixture() + @pytest.fixture def mock_time(self, mocker): m = mocker.patch('qutebrowser.browser.history.time') m.time.return_value = 12345 diff --git a/tests/unit/browser/test_inspector.py b/tests/unit/browser/test_inspector.py index f7f532050..61ac9510d 100644 --- a/tests/unit/browser/test_inspector.py +++ b/tests/unit/browser/test_inspector.py @@ -151,4 +151,5 @@ def test_detach_after_toggling(hidden_again, needs_recreate, else: with qtbot.assert_not_emitted(fake_inspector.recreate): fake_inspector.set_position(inspector.Position.window) - assert fake_inspector.isVisible() and fake_inspector.isWindow() + assert fake_inspector.isVisible() + assert fake_inspector.isWindow() diff --git a/tests/unit/browser/webkit/http/test_http.py b/tests/unit/browser/webkit/http/test_http.py index 4db78f4ff..d50f1c277 100644 --- a/tests/unit/browser/webkit/http/test_http.py +++ b/tests/unit/browser/webkit/http/test_http.py @@ -44,6 +44,21 @@ def test_no_content_disposition(stubs, url, expected): assert filename == expected +@pytest.mark.parametrize('value', [ + # https://github.com/python/cpython/issues/87112 + 'inline; 0*²'.encode("iso-8859-1"), + # https://github.com/python/cpython/issues/81672 + b'"', + # https://github.com/python/cpython/issues/93010 + b'attachment; 0*00="foo"', + # FIXME: Should probably have more tests if this is still relevant after + # dropping QtWebKit. +]) +def test_parse_content_disposition_invalid(value): + with pytest.raises(http.ContentDispositionError): + http.ContentDisposition.parse(value) + + @pytest.mark.parametrize('template', [ '{}', 'attachment; filename="{}"', diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py index 593896e96..7df91922d 100644 --- a/tests/unit/browser/webkit/test_webkitelem.py +++ b/tests/unit/browser/webkit/test_webkitelem.py @@ -181,13 +181,31 @@ class SelectionAndFilterTests: # We can't easily test <frame>/<iframe> as they vanish when setting # them via QWebFrame::setHtml... + ('<img src="bar">', ['all', 'images', 'url']), + ('<summary>bar</summary>', ['all']), + ('<link />', ['all']), + + ('<p contenteditable />', ['all', 'inputs']), + ('<p contenteditable="false" />', []), ('<p onclick="foo" foo="bar"/>', ['all']), ('<p onmousedown="foo" foo="bar"/>', ['all']), ('<p role="option" foo="bar"/>', ['all']), + ('<p role="tab" foo="bar"/>', ['all']), + ('<p role="checkbox" foo="bar"/>', ['all']), + ('<p role="menuitem" foo="bar"/>', ['all']), + ('<p role="menuitemcheckbox" foo="bar"/>', ['all']), + ('<p role="menuitemradio" foo="bar"/>', ['all']), + ('<p role="treeitem" foo="bar"/>', ['all']), ('<p role="button" foo="bar"/>', ['all']), ('<p role="button" href="bar"/>', ['all', 'url']), - ('<span tabindex=0 />', ['all']), + ('<span tabindex="0" />', ['all']), + ('<span tabindex="-1" />', []), + + ('<span ng-click=""></span>', ['all']), + ('<span ngClick=""></span>', ['all']), + ('<span data-ng-click=""></span>', ['all']), + ('<span x-ng-click=""></span>', ['all']), ] GROUPS = ['all', 'links', 'images', 'url', 'inputs'] @@ -849,8 +867,6 @@ class TestIsEditable: @pytest.mark.parametrize('setting, tagname, attributes, editable', [ (True, 'embed', {}, True), - (True, 'embed', {}, True), - (False, 'applet', {}, False), (False, 'applet', {}, False), (True, 'object', {'type': 'application/foo'}, True), (False, 'object', {'type': 'application/foo'}, False), diff --git a/tests/unit/commands/test_cmdexc.py b/tests/unit/commands/test_cmdexc.py new file mode 100644 index 000000000..f80131e29 --- /dev/null +++ b/tests/unit/commands/test_cmdexc.py @@ -0,0 +1,40 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2022 Florian Bruhin (The Compiler) <mail@qutebrowser.org> +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. + +"""Tests for qutebrowser.commands.cmdexc.""" + +import re +import pytest + +from qutebrowser.commands import cmdexc + + +def test_empty_command_error(): + with pytest.raises(cmdexc.NoSuchCommandError, match="No command given"): + raise cmdexc.EmptyCommandError + + +@pytest.mark.parametrize("all_commands, msg", [ + ([], "testcmd: no such command"), + (["fastcmd"], "testcmd: no such command (did you mean :fastcmd?)"), + (["thisdoesnotmatch"], "testcmd: no such command"), +]) +def test_no_such_command_error(all_commands, msg): + with pytest.raises(cmdexc.NoSuchCommandError, match=re.escape(msg)): + raise cmdexc.NoSuchCommandError.for_cmd("testcmd", all_commands=all_commands) diff --git a/tests/unit/commands/test_parser.py b/tests/unit/commands/test_parser.py index b851ad3b0..a0c2fe8f3 100644 --- a/tests/unit/commands/test_parser.py +++ b/tests/unit/commands/test_parser.py @@ -19,6 +19,8 @@ """Tests for qutebrowser.commands.parser.""" +import re + import pytest from qutebrowser.misc import objects @@ -64,7 +66,7 @@ class TestCommandParser: and https://github.com/qutebrowser/qutebrowser/issues/1773 """ p = parser.CommandParser() - with pytest.raises(cmdexc.NoSuchCommandError): + with pytest.raises(cmdexc.EmptyCommandError): p.parse_all(command) @pytest.mark.parametrize('command, name, args', [ @@ -135,3 +137,13 @@ class TestCompletions: result = p.parse('tw') assert result.cmd.name == 'two' + + +@pytest.mark.parametrize("find_similar, msg", [ + (True, "tabfocus: no such command (did you mean :tab-focus?)"), + (False, "tabfocus: no such command"), +]) +def test_find_similar(find_similar, msg): + p = parser.CommandParser(find_similar=find_similar) + with pytest.raises(cmdexc.NoSuchCommandError, match=re.escape(msg)): + p.parse_all("tabfocus", aliases=False) diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index 467357d32..0f59f50ab 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -22,10 +22,9 @@ import unittest.mock import pytest -from PyQt5.QtCore import QObject from PyQt5.QtGui import QStandardItemModel -from qutebrowser.completion import completer +from qutebrowser.completion import completer, completionwidget from qutebrowser.commands import command from qutebrowser.api import cmdutils @@ -48,12 +47,12 @@ class FakeCompletionModel(QStandardItemModel): self.info = info -class CompletionWidgetStub(QObject): +class CompletionWidgetStub(completionwidget.CompletionView): """Stub for the CompletionView.""" def __init__(self, parent=None): - super().__init__(parent) + super().__init__(cmd=None, win_id=0, parent=parent) self.hide = unittest.mock.Mock() self.show = unittest.mock.Mock() self.set_pattern = unittest.mock.Mock() diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py index 89390cbf1..074228332 100644 --- a/tests/unit/completion/test_completionwidget.py +++ b/tests/unit/completion/test_completionwidget.py @@ -37,13 +37,13 @@ def completionview(qtbot, status_command_stub, config_stub, win_registry, mocker.patch('qutebrowser.completion.completer.Completer', autospec=True) mocker.patch( 'qutebrowser.completion.completiondelegate.CompletionItemDelegate', - new=lambda *_: None) + return_value=None) view = completionwidget.CompletionView(cmd=status_command_stub, win_id=0) qtbot.add_widget(view) return view -@pytest.fixture() +@pytest.fixture def model(): return completionmodel.CompletionModel() @@ -163,16 +163,7 @@ def test_completion_item_focus_no_model(which, completionview, model, qtbot): @pytest.mark.skip("Seems to disagree with reality, see #5897") def test_completion_item_focus_fetch(completionview, model, qtbot): - """Test that on_next_prev_item moves the selection properly. - - Args: - which: the direction in which to move the selection. - tree: Each list represents a completion category, with each string - being an item under that category. - expected: expected argument from on_selection_changed for each - successive movement. None implies no signal should be - emitted. - """ + """Test that on_next_prev_item moves the selection properly.""" cat = mock.Mock(spec=[ 'layoutChanged', 'layoutAboutToBeChanged', 'canFetchMore', 'fetchMore', 'rowCount', 'index', 'data']) diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index c20fe293c..2c00acf68 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -76,7 +76,7 @@ def _check_completions(model, expected): assert sum(model.column_widths) == 100 -@pytest.fixture() +@pytest.fixture def cmdutils_stub(monkeypatch, stubs): """Patch the cmdutils module to provide fake commands.""" return monkeypatch.setattr(objects, 'commands', { @@ -93,7 +93,7 @@ def cmdutils_stub(monkeypatch, stubs): }) -@pytest.fixture() +@pytest.fixture def configdata_stub(config_stub, monkeypatch, configdata_init): """Patch the configdata module to provide fake data.""" monkeypatch.setattr(configdata, 'DATA', collections.OrderedDict([ @@ -867,6 +867,34 @@ def test_tab_completion_delete(qtmodeltester, fake_web_tab, win_registry, QUrl('https://duckduckgo.com')] +def test_tab_focus_completion_delete(qtmodeltester, fake_web_tab, win_registry, + tabbed_browser_stubs, info): + """Verify closing a tab by deleting it from the completion widget.""" + tabbed_browser_stubs[0].widget.tabs = [ + fake_web_tab(QUrl('https://github.com'), 'GitHub', 0), + fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1), + fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2) + ] + tabbed_browser_stubs[1].widget.tabs = [ + fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0), + ] + model = miscmodels.tab_focus(info=info) + model.set_pattern('') + qtmodeltester.check(model) + + parent = model.index(0, 0) + idx = model.index(1, 0, parent) + + # sanity checks + assert model.data(parent) == "Tabs" + assert model.data(idx) == '2' + + model.delete_cur_item(idx) + actual = [tab.url() for tab in tabbed_browser_stubs[0].widget.tabs] + assert actual == [QUrl('https://github.com'), + QUrl('https://duckduckgo.com')] + + def test_tab_completion_not_sorted(qtmodeltester, fake_web_tab, win_registry, tabbed_browser_stubs): """Ensure that the completion row order is the same as tab index order. diff --git a/tests/unit/components/test_hostblock.py b/tests/unit/components/test_hostblock.py index 00a7a5f8f..5949f92f8 100644 --- a/tests/unit/components/test_hostblock.py +++ b/tests/unit/components/test_hostblock.py @@ -427,7 +427,7 @@ def test_invalid_utf8(config_stub, tmp_path, caplog, host_blocker_factory, locat with caplog.at_level(logging.ERROR): current_download.successful = True current_download.finished.emit() - expected = r"Failed to decode: " r"b'https://www.example.org/\xa0localhost" + expected = r"Failed to decode: b'https://www.example.org/\xa0localhost" assert caplog.messages[-2].startswith(expected) else: current_download.successful = True @@ -565,9 +565,13 @@ def test_adblock_benchmark(data_tmpdir, benchmark, host_blocker_factory): benchmark(lambda: blocker._is_blocked(url)) -def test_subdomain_blocking(config_stub, host_blocker_factory): +@pytest.mark.parametrize("block_subdomains", [True, False]) +def test_subdomain_blocking(config_stub, host_blocker_factory, block_subdomains): config_stub.val.content.blocking.method = "hosts" config_stub.val.content.blocking.hosts.lists = None + config_stub.val.content.blocking.hosts.block_subdomains = block_subdomains host_blocker = host_blocker_factory() host_blocker._blocked_hosts.add("example.com") - assert host_blocker._is_blocked(QUrl("https://subdomain.example.com")) + is_blocked = host_blocker._is_blocked(QUrl("https://subdomain.example.com")) + # block_subdomains is also expected result of is_blocked + assert is_blocked == block_subdomains diff --git a/tests/unit/components/test_readlinecommands.py b/tests/unit/components/test_readlinecommands.py index 334e2ef9e..af815b075 100644 --- a/tests/unit/components/test_readlinecommands.py +++ b/tests/unit/components/test_readlinecommands.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. +import os import re import inspect @@ -94,18 +95,19 @@ class LineEdit(QLineEdit): return ''.join(chars) -def _validate_deletion(lineedit, method, text, deleted, rest): +def _validate_deletion(lineedit, method, args, text, deleted, rest): """Run and validate a text deletion method on the ReadLine bridge. Args: lineedit: The LineEdit instance. method: Reference to the method on the bridge to test. + args: Arguments to pass to the method. text: The starting 'augmented' text (see LineEdit.set_aug_text) deleted: The text that should be deleted when the method is invoked. rest: The augmented text that should remain after method is invoked. """ lineedit.set_aug_text(text) - method() + method(*args) assert readlinecommands.bridge._deleted[lineedit] == deleted assert lineedit.aug_text() == rest lineedit.clear() @@ -127,7 +129,9 @@ def test_none(qtbot): assert QApplication.instance().focusWidget() is None for name, method in inspect.getmembers(readlinecommands, inspect.isfunction): - if name.startswith('rl_'): + if name == "rl_rubout": + method(delim=" ") + elif name.startswith('rl_'): method() @@ -218,7 +222,8 @@ def test_rl_backward_delete_char(text, expected, lineedit): ]) def test_rl_unix_line_discard(lineedit, text, deleted, rest): """Delete from the cursor to the beginning of the line and yank back.""" - _validate_deletion(lineedit, readlinecommands.rl_unix_line_discard, + _validate_deletion(lineedit, + readlinecommands.rl_unix_line_discard, [], text, deleted, rest) @@ -230,7 +235,8 @@ def test_rl_unix_line_discard(lineedit, text, deleted, rest): ]) def test_rl_kill_line(lineedit, text, deleted, rest): """Delete from the cursor to the end of line and yank back.""" - _validate_deletion(lineedit, readlinecommands.rl_kill_line, + _validate_deletion(lineedit, + readlinecommands.rl_kill_line, [], text, deleted, rest) @@ -243,9 +249,14 @@ def test_rl_kill_line(lineedit, text, deleted, rest): marks=fixme), ('test del<ete >foobar', 'del', 'test |ete foobar'), # wrong ]) -def test_rl_unix_word_rubout(lineedit, text, deleted, rest): +@pytest.mark.parametrize("method, args", [ + (readlinecommands.rl_unix_word_rubout, []), # deprecated + (readlinecommands.rl_rubout, [" "]), # equivalent +]) +def test_rl_unix_word_rubout(lineedit, text, deleted, rest, method, args): """Delete to word beginning and see if it comes back with yank.""" - _validate_deletion(lineedit, readlinecommands.rl_unix_word_rubout, + _validate_deletion(lineedit, + method, args, text, deleted, rest) @@ -256,9 +267,35 @@ def test_rl_unix_word_rubout(lineedit, text, deleted, rest): ('open -t |github.com/foo/bar', '-t ', 'open |github.com/foo/bar'), ('open foo/bar.baz|', 'bar.baz', 'open foo/|'), ]) -def test_rl_unix_filename_rubout(lineedit, text, deleted, rest): +@pytest.mark.parametrize("method, args", [ + (readlinecommands.rl_unix_filename_rubout, []), # deprecated + (readlinecommands.rl_rubout, [" /"]), # equivalent +]) +def test_rl_unix_filename_rubout(lineedit, text, deleted, rest, method, args): + """Delete filename segment and see if it comes back with yank.""" + _validate_deletion(lineedit, + method, args, + text, deleted, rest) + + +@pytest.mark.parametrize('os_sep, text, deleted, rest', [ + ('/', 'path|', 'path', '|'), + ('/', '/path|', 'path', '/|'), + ('/', '/path/sub|', 'sub', '/path/|'), + ('/', '/path/trailing/|', 'trailing/', '/path/|'), + ('/', '/test/path with spaces|', 'path with spaces', '/test/|'), + ('/', r'/test/path\backslashes\eww|', r'path\backslashes\eww', '/test/|'), + ('\\', 'path|', 'path', '|'), + ('\\', r'C:\path|', 'path', r'C:\|'), + ('\\', r'C:\path\sub|', 'sub', r'C:\path\|'), + ('\\', r'C:\test\path with spaces|', 'path with spaces', r'C:\test\|'), + ('\\', r'C:\path\trailing\|', 'trailing\\', r'C:\path\|'), +]) +def test_filename_rubout(os_sep, monkeypatch, lineedit, text, deleted, rest): """Delete filename segment and see if it comes back with yank.""" - _validate_deletion(lineedit, readlinecommands.rl_unix_filename_rubout, + monkeypatch.setattr(os, "sep", os_sep) + _validate_deletion(lineedit, + readlinecommands.rl_filename_rubout, [], text, deleted, rest) @@ -275,7 +312,8 @@ def test_rl_unix_filename_rubout(lineedit, text, deleted, rest): ]) def test_rl_kill_word(lineedit, text, deleted, rest): """Delete to word end and see if it comes back with yank.""" - _validate_deletion(lineedit, readlinecommands.rl_kill_word, + _validate_deletion(lineedit, + readlinecommands.rl_kill_word, [], text, deleted, rest) @@ -290,7 +328,8 @@ def test_rl_kill_word(lineedit, text, deleted, rest): ]) def test_rl_backward_kill_word(lineedit, text, deleted, rest): """Delete to word beginning and see if it comes back with yank.""" - _validate_deletion(lineedit, readlinecommands.rl_backward_kill_word, + _validate_deletion(lineedit, + readlinecommands.rl_backward_kill_word, [], text, deleted, rest) diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index b88bc2f8d..ab1fba789 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -782,7 +782,7 @@ class TestContainer: assert len(configapi.errors) == 1 error = configapi.errors[0] assert error.text == "While getting 'tabs.foobar'" - assert str(error.exception) == "No option 'tabs.foobar'" + assert str(error.exception).startswith("No option 'tabs.foobar'") def test_confapi_missing_prefix(self, container): configapi = types.SimpleNamespace(errors=[]) @@ -793,7 +793,7 @@ class TestContainer: error1 = configapi.errors[0] assert error1.text == "While getting 'content.host_blocking'" - assert str(error1.exception) == "No option 'content.host_blocking'" + assert str(error1.exception).startswith("No option 'content.host_blocking'") error2 = configapi.errors[1] assert error2.text == "While setting 'content.host_blocking.lists'" diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py index 0375503e8..6dd09cee3 100644 --- a/tests/unit/config/test_configcommands.py +++ b/tests/unit/config/test_configcommands.py @@ -27,7 +27,7 @@ from PyQt5.QtCore import QUrl from qutebrowser.config import configcommands from qutebrowser.api import cmdutils -from qutebrowser.utils import usertypes, urlmatch +from qutebrowser.utils import usertypes, urlmatch, utils from qutebrowser.keyinput import keyutils from qutebrowser.misc import objects @@ -529,7 +529,7 @@ class TestSource: pyfile = config_tmpdir / 'sourced.py' arg = 'sourced.py' else: - assert False, location + raise utils.Unreachable(location) pyfile.write_text('\n'.join(['config.load_autoconfig(False)', 'c.content.javascript.enabled = False']), @@ -725,15 +725,19 @@ class TestBind: """Get a dict with no bindings.""" return {'normal': {}} + @pytest.mark.parametrize("mode, url", [ + ("normal", QUrl("qute://bindings")), + ("passthrough", QUrl("qute://bindings#passthrough")), + ]) def test_bind_no_args(self, commands, config_stub, no_bindings, - tabbed_browser_stubs): + tabbed_browser_stubs, mode, url): """Run ':bind'. Should open qute://bindings.""" config_stub.val.bindings.default = no_bindings config_stub.val.bindings.commands = no_bindings - commands.bind(win_id=0) - assert tabbed_browser_stubs[0].loaded_url == QUrl('qute://bindings') + commands.bind(win_id=0, mode=mode) + assert tabbed_browser_stubs[0].loaded_url == url @pytest.mark.parametrize('command', ['nop', 'nope']) def test_bind(self, commands, config_stub, no_bindings, key_config_stub, diff --git a/tests/unit/config/test_configexc.py b/tests/unit/config/test_configexc.py index a236494f9..b289d3cc8 100644 --- a/tests/unit/config/test_configexc.py +++ b/tests/unit/config/test_configexc.py @@ -32,13 +32,27 @@ def test_validation_error(): assert str(e) == "Invalid value 'val' - msg" -@pytest.mark.parametrize('deleted, renamed, expected', [ - (False, None, "No option 'opt'"), - (True, None, "No option 'opt' (this option was removed from qutebrowser)"), - (False, 'new', "No option 'opt' (this option was renamed to 'new')"), +@pytest.mark.parametrize('deleted, renamed, all_names, expected', [ + (False, None, [], "No option 'opt'"), + (True, None, [], "No option 'opt' (this option was removed from qutebrowser)"), + (False, 'new', [], "No option 'opt' (this option was renamed to 'new')"), + (False, None, ["opto"], "No option 'opt' (did you mean 'opto'?)"), + (False, None, ["thisdoesnotmatch"], "No option 'opt'"), + ( + True, + None, + ["opto"], + "No option 'opt' (this option was removed from qutebrowser)", + ), + (False, 'new', ["opto"], "No option 'opt' (this option was renamed to 'new')"), ]) -def test_no_option_error(deleted, renamed, expected): - e = configexc.NoOptionError('opt', deleted=deleted, renamed=renamed) +def test_no_option_error(deleted, renamed, all_names, expected): + e = configexc.NoOptionError( + 'opt', + deleted=deleted, + renamed=renamed, + all_names=all_names, + ) assert e.option == 'opt' assert str(e) == expected diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index 65952ddb4..760992e15 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -873,7 +873,7 @@ class TestConfigPyModules: confpy.read() expected = {'normal': {',a': 'message-info foo'}} assert config.instance.get_obj('bindings.commands') == expected - assert "qbmodule" not in sys.modules.keys() + assert "qbmodule" not in sys.modules assert tmp_path not in sys.path def test_restore_sys_on_err(self, confpy, qbmodulepy, tmp_path): @@ -884,7 +884,7 @@ class TestConfigPyModules: assert error.text == "Unhandled exception" assert isinstance(error.exception, ZeroDivisionError) - assert "qbmodule" not in sys.modules.keys() + assert "qbmodule" not in sys.modules assert tmp_path not in sys.path def test_fail_on_nonexistent_module(self, confpy, qbmodulepy, tmp_path): @@ -1481,7 +1481,6 @@ class TestConfigPyWriter: def init_patch(qapp, fake_save_manager, config_tmpdir, data_tmpdir, config_stub, monkeypatch): monkeypatch.setattr(configfiles, 'state', None) - yield def test_init(init_patch, config_tmpdir): diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 66b152937..e4351d619 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -19,7 +19,6 @@ """Tests for qutebrowser.config.configtypes.""" import re -import sys import json import math import warnings @@ -224,6 +223,7 @@ class TestAll: """Patch aliases so Command works.""" config_stub.val.aliases = {} + # pylint: disable-next=too-many-function-args @pytest.fixture(params=list(gen_classes())) def klass(self, request): return request.param @@ -1501,12 +1501,8 @@ class TestRegex: pytest.param('(' * 500, id='too many parens'), pytest.param(r'foo\Xbar', id='invalid escape X'), pytest.param(r'foo\Cbar', id='invalid escape C'), - pytest.param(r'[[]]', id='nested set', marks=pytest.mark.skipif( - sys.hexversion < 0x03070000, - reason="Warning was added in Python 3.7")), - pytest.param(r'[a||b]', id='set operation', marks=pytest.mark.skipif( - sys.hexversion < 0x03070000, - reason="Warning was added in Python 3.7")), + pytest.param(r'[[]]', id='nested set'), + pytest.param(r'[a||b]', id='set operation'), ]) def test_to_py_invalid(self, klass, val): with pytest.raises(configexc.ValidationError): @@ -1876,7 +1872,6 @@ class TestShellCommand: @pytest.mark.parametrize('kwargs, val', [ ({'placeholder': True}, '[foo, bar]'), ({'placeholder': True}, '[foo, "{", "}", bar'), - ({'placeholder': True}, '[foo, bar]'), ({'placeholder': True}, '[foo, "{fi", "le}", bar'), # Like valid ones but with wrong placeholder diff --git a/tests/unit/config/test_configutils.py b/tests/unit/config/test_configutils.py index e7ce15aff..3fc5cd561 100644 --- a/tests/unit/config/test_configutils.py +++ b/tests/unit/config/test_configutils.py @@ -318,13 +318,11 @@ class TestFontFamilies: @pytest.mark.parametrize('families, quote, expected', [ (['family'], True, 'family'), (['family1', 'family2'], True, 'family1, family2'), - (['family'], True, 'family'), (['space family', 'alien'], True, '"space family", alien'), (['comma,family', 'period'], True, '"comma,family", period'), (['family'], False, 'family'), (['family1', 'family2'], False, 'family1, family2'), - (['family'], False, 'family'), (['space family', 'alien'], False, 'space family, alien'), (['comma,family', 'period'], False, 'comma,family, period'), ]) @@ -367,9 +365,13 @@ class TestFontFamilies: # Check the requested font to make sure CSS parsing worked assert label.font().family() == families.family + # Skipping the rest of the test as WORKAROUND for + # https://bugreports.qt.io/browse/QTBUG-94090 + return + # Try to find out whether the monospace font did a fallback on a non-monospace # font... - fallback_label = QLabel() + fallback_label = QLabel() # pylint: disable=unreachable qtbot.add_widget(label) fallback_label.setText("fallback") @@ -382,9 +384,5 @@ class TestFontFamilies: if info.family() == fallback_family: return - if info.family() == 'Noto Sans Mono': - # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-94090 - return - # If we didn't fall back, we should've gotten a fixed-pitch font. assert info.fixedPitch(), info.family() diff --git a/tests/unit/config/test_qtargs.py b/tests/unit/config/test_qtargs.py index 4c31c5b07..076ff6e3c 100644 --- a/tests/unit/config/test_qtargs.py +++ b/tests/unit/config/test_qtargs.py @@ -249,7 +249,7 @@ class TestWebEngineArgs: ('single-process', True), ]) def test_process_model(self, config_stub, parser, process_model, added): - config_stub.val.qt.process_model = process_model + config_stub.val.qt.chromium.process_model = process_model parsed = parser.parse_args([]) args = qtargs.qt_args(parsed) @@ -267,7 +267,7 @@ class TestWebEngineArgs: ('never', '--disable-low-end-device-mode'), ]) def test_low_end_device_mode(self, config_stub, parser, low_end_device_mode, arg): - config_stub.val.qt.low_end_device_mode = low_end_device_mode + config_stub.val.qt.chromium.low_end_device_mode = low_end_device_mode parsed = parser.parse_args([]) args = qtargs.qt_args(parsed) @@ -277,6 +277,28 @@ class TestWebEngineArgs: else: assert arg in args + @pytest.mark.parametrize('sandboxing, arg', [ + ('enable-all', None), + ('disable-seccomp-bpf', '--disable-seccomp-filter-sandbox'), + ('disable-all', '--no-sandbox'), + ]) + def test_sandboxing(self, config_stub, parser, sandboxing, arg): + config_stub.val.qt.chromium.sandboxing = sandboxing + parsed = parser.parse_args([]) + args = qtargs.qt_args(parsed) + + remaining_flags = { + '--no-sandbox', + '--disable-seccomp-filter-sandbox', + } + if arg is not None: + remaining_flags.remove(arg) + + if arg is not None: + assert arg in args + + assert not set(args) & remaining_flags + @pytest.mark.parametrize('qt_version, referer, arg', [ # 'always' -> no arguments ('5.15.0', 'always', None), @@ -324,7 +346,6 @@ class TestWebEngineArgs: ("light", "5.15.0", False), ("light", "5.15.1", False), ("light", "5.15.2", False), - ("light", "5.15.2", False), ("light", "5.15.3", False), ("light", "6.0.0", False), @@ -333,7 +354,6 @@ class TestWebEngineArgs: ("auto", "5.15.0", False), ("auto", "5.15.1", False), ("auto", "5.15.2", False), - ("auto", "5.15.2", False), ("auto", "5.15.3", False), ("auto", "6.0.0", False), ]) diff --git a/tests/unit/config/test_websettings.py b/tests/unit/config/test_websettings.py index 7601c7c24..f42563727 100644 --- a/tests/unit/config/test_websettings.py +++ b/tests/unit/config/test_websettings.py @@ -24,7 +24,7 @@ from qutebrowser.misc import objects from qutebrowser.utils import usertypes -@pytest.mark.parametrize([ +@pytest.mark.parametrize([ # noqa: PT006 'user_agent', 'os_info', 'webkit_version', 'upstream_browser_key', 'upstream_browser_version', 'qt_key' ], [ diff --git a/tests/unit/extensions/test_loader.py b/tests/unit/extensions/test_loader.py index feb5dd347..e9b8055aa 100644 --- a/tests/unit/extensions/test_loader.py +++ b/tests/unit/extensions/test_loader.py @@ -35,16 +35,10 @@ def test_on_walk_error(): def test_walk_normal(): - names = [info.name for info in loader._walk_normal()] + names = [info.name for info in loader.walk_components()] assert 'qutebrowser.components.scrollcommands' in names -def test_walk_pyinstaller(): - # We can't test whether we get something back without being frozen by - # PyInstaller, but at least we can test that we don't crash. - list(loader._walk_pyinstaller()) - - def test_load_component(monkeypatch): monkeypatch.setattr(objects, 'commands', {}) diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index 30ee36301..84068bf47 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -346,3 +346,14 @@ def test_clear_keystring_empty(qtbot, keyparser): keyparser._sequence = keyseq('') with qtbot.assert_not_emitted(keyparser.keystring_updated): keyparser.clear_keystring() + + +def test_respect_config_when_matching_counts(keyparser, config_stub): + """Don't match counts if disabled in the config.""" + config_stub.val.input.match_counts = False + + info = keyutils.KeyInfo(Qt.Key_1, Qt.NoModifier) + keyparser.handle(info.to_event()) + + assert not keyparser._sequence + assert not keyparser._count diff --git a/tests/unit/mainwindow/statusbar/test_textbase.py b/tests/unit/mainwindow/statusbar/test_textbase.py index 631c6ce44..33c4ffd76 100644 --- a/tests/unit/mainwindow/statusbar/test_textbase.py +++ b/tests/unit/mainwindow/statusbar/test_textbase.py @@ -79,11 +79,11 @@ def test_text_elide_none(mocker, qtbot): label = TextBase() qtbot.add_widget(label) label.setText('') - mocker.patch('qutebrowser.mainwindow.statusbar.textbase.TextBase.' - 'fontMetrics') + mock = mocker.patch( + 'qutebrowser.mainwindow.statusbar.textbase.TextBase.fontMetrics') label._update_elided_text(20) - assert not label.fontMetrics.called + assert not mock.called def test_unset_text(qtbot): diff --git a/tests/unit/mainwindow/test_prompt.py b/tests/unit/mainwindow/test_prompt.py index 5b774bdaa..3218959a5 100644 --- a/tests/unit/mainwindow/test_prompt.py +++ b/tests/unit/mainwindow/test_prompt.py @@ -48,9 +48,9 @@ class TestFileCompletion: return _get_prompt_func @pytest.mark.parametrize('steps, where, subfolder', [ - (1, 'next', '..'), + (1, 'next', 'a'), (1, 'prev', 'c'), - (2, 'next', 'a'), + (2, 'next', 'b'), (2, 'prev', 'b'), ]) def test_simple_completion(self, tmp_path, get_prompt, steps, where, @@ -84,9 +84,7 @@ class TestFileCompletion: # For some reason, this isn't always called when using qtbot.keyPress. prompt._set_fileview_root(prompt._lineedit.text()) - # '..' and 'foo' should get completed from 'f' - prompt.item_focus('next') - assert prompt._lineedit.text() == str(tmp_path) + # 'foo' should get completed from 'f' prompt.item_focus('next') assert prompt._lineedit.text() == str(testdir / 'foo') @@ -94,15 +92,15 @@ class TestFileCompletion: for _ in range(3): qtbot.keyPress(prompt._lineedit, Qt.Key_Backspace) - # We should now show / again, so tabbing twice gives us .. -> bar + # We should now show / again, so tabbing twice gives us bar -> foo prompt.item_focus('next') prompt.item_focus('next') - assert prompt._lineedit.text() == str(testdir / 'bar') + assert prompt._lineedit.text() == str(testdir / 'foo') @pytest.mark.parametrize("keys, expected", [ - ([], ['..', 'bar', 'bat', 'foo']), - ([Qt.Key_F], ['..', 'foo']), - ([Qt.Key_A], ['..', 'bar', 'bat']), + ([], ['bar', 'bat', 'foo']), + ([Qt.Key_F], ['foo']), + ([Qt.Key_A], ['bar', 'bat']), ]) def test_filtering_path(self, qtbot, tmp_path, get_prompt, keys, expected): testdir = tmp_path / 'test' diff --git a/tests/unit/misc/test_checkpyver.py b/tests/unit/misc/test_checkpyver.py index 52e022a44..35f8cfeec 100644 --- a/tests/unit/misc/test_checkpyver.py +++ b/tests/unit/misc/test_checkpyver.py @@ -28,19 +28,18 @@ import pytest from qutebrowser.misc import checkpyver -TEXT = (r"At least Python 3.6.1 is required to run qutebrowser, but it's " +TEXT = (r"At least Python 3.7 is required to run qutebrowser, but it's " r"running with \d+\.\d+\.\d+.") @pytest.mark.not_frozen -@pytest.mark.parametrize('python', ['python2', 'python3.5']) +@pytest.mark.parametrize('python', ['python2', 'python3.6']) def test_old_python(python): """Run checkpyver with old python versions.""" try: proc = subprocess.run( [python, checkpyver.__file__, '--no-err-windows'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, check=False) except FileNotFoundError: pytest.skip(f"{python} not found") diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index 8e5597a0e..91bdce26b 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -19,6 +19,7 @@ """Tests for qutebrowser.misc.editor.""" +import sys import time import pathlib import os @@ -55,31 +56,33 @@ class TestArg: def test_placeholder(self, config_stub, editor): """Test starting editor with placeholder argument.""" - config_stub.val.editor.command = ['bin', 'foo', '{}', 'bar'] + config_stub.val.editor.command = [sys.executable, 'foo', '{}', 'bar'] editor.edit("") editor._proc._proc.start.assert_called_with( - "bin", ["foo", editor._filename, "bar"]) + sys.executable, ["foo", editor._filename, "bar"]) def test_placeholder_inline(self, config_stub, editor): """Test starting editor with placeholder arg inside of another arg.""" - config_stub.val.editor.command = ['bin', 'foo{}', 'bar'] + config_stub.val.editor.command = [sys.executable, 'foo{}', 'bar'] editor.edit("") editor._proc._proc.start.assert_called_with( - "bin", ["foo" + editor._filename, "bar"]) + sys.executable, ["foo" + editor._filename, "bar"]) class TestFileHandling: """Test creation/deletion of tempfile.""" - def test_ok(self, editor): + @pytest.mark.parametrize('remove_file', [True, False]) + def test_ok(self, editor, remove_file, config_stub): """Test file handling when closing with an exit status == 0.""" + config_stub.val.editor.remove_file = remove_file editor.edit("") filename = pathlib.Path(editor._filename) assert filename.exists() assert filename.name.startswith('qutebrowser-editor-') editor._proc._proc.finished.emit(0, QProcess.NormalExit) - assert not filename.exists() + assert filename.exists() != config_stub.val.editor.remove_file @pytest.mark.parametrize('touch', [True, False]) def test_with_filename(self, editor, tmp_path, touch): diff --git a/tests/unit/misc/test_elf.py b/tests/unit/misc/test_elf.py index 86060bbde..7d3248da2 100644 --- a/tests/unit/misc/test_elf.py +++ b/tests/unit/misc/test_elf.py @@ -75,6 +75,23 @@ def test_result(qapp, caplog): assert ua.upstream_browser_version == versions.chromium +@pytest.mark.parametrize("data, expected", [ + # Simple match + ( + b"\x00QtWebEngine/5.15.9 Chrome/87.0.4280.144\x00", + elf.Versions("5.15.9", "87.0.4280.144"), + ), + # Ignoring garbage string-like data + ( + b"\x00QtWebEngine/5.15.9 Chrome/87.0.4xternalclearkey\x00\x00" + b"QtWebEngine/5.15.9 Chrome/87.0.4280.144\x00", + elf.Versions("5.15.9", "87.0.4280.144"), + ), +]) +def test_find_versions(data, expected): + assert elf._find_versions(data) == expected + + @hypothesis.given(data=hst.builds( lambda *a: b''.join(a), hst.sampled_from([b'', b'\x7fELF', b'\x7fELF\x02\x01\x01']), diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index be86bf215..aaff5154e 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -26,12 +26,12 @@ import pytest from PyQt5.QtCore import QProcess, QUrl from qutebrowser.misc import guiprocess -from qutebrowser.utils import usertypes, utils +from qutebrowser.utils import usertypes, utils, version from qutebrowser.api import cmdutils from qutebrowser.qt import sip -@pytest.fixture() +@pytest.fixture def proc(qtbot, caplog): """A fixture providing a GUIProcess and cleaning it up after the test.""" p = guiprocess.GUIProcess('testprocess') @@ -46,7 +46,7 @@ def proc(qtbot, caplog): p._proc.waitForFinished() -@pytest.fixture() +@pytest.fixture def fake_proc(monkeypatch, stubs): """A fixture providing a GUIProcess with a mocked QProcess.""" p = guiprocess.GUIProcess('testprocess') @@ -319,8 +319,8 @@ def test_start_env(monkeypatch, qtbot, py_proc): def test_start_detached(fake_proc): """Test starting a detached process.""" - cmd = 'foo' - args = ['bar'] + cmd = sys.executable + args = ['--version'] fake_proc._proc.startDetached.return_value = (True, 0) fake_proc.start_detached(cmd, args) fake_proc._proc.startDetached.assert_called_with(cmd, args, None) @@ -394,8 +394,11 @@ def test_running(qtbot, proc, py_proc): proc.outcome.was_successful() -def test_failing_to_start(qtbot, proc, caplog, message_mock): +@pytest.mark.parametrize('is_flatpak', [True, False]) +def test_failing_to_start(qtbot, proc, caplog, message_mock, monkeypatch, is_flatpak): """Test the process failing to start.""" + monkeypatch.setattr(version, 'is_flatpak', lambda: is_flatpak) + with caplog.at_level(logging.ERROR, 'message'): with qtbot.wait_signal(proc.error, timeout=5000): proc.start('this_does_not_exist_either', []) @@ -405,8 +408,11 @@ def test_failing_to_start(qtbot, proc, caplog, message_mock): "Testprocess 'this_does_not_exist_either' failed to start:") if not utils.is_windows: - assert msg.text.endswith( - "(Hint: Make sure 'this_does_not_exist_either' exists and is executable)") + expected_msg = ( + "Hint: Make sure 'this_does_not_exist_either' exists and is executable") + if is_flatpak: + expected_msg += ' inside the Flatpak container' + assert msg.text.endswith(expected_msg) assert not proc.outcome.running assert proc.outcome.status is None diff --git a/tests/unit/misc/test_keyhints.py b/tests/unit/misc/test_keyhints.py index 8d9c53c93..922f39331 100644 --- a/tests/unit/misc/test_keyhints.py +++ b/tests/unit/misc/test_keyhints.py @@ -30,8 +30,8 @@ def expected_text(*args): """Helper to format text we expect the KeyHintView to generate. Args: - args: One tuple for each row in the expected output. - Tuples are of the form: (prefix, color, suffix, command). + *args: One tuple for each row in the expected output. + Tuples are of the form: (prefix, color, suffix, command). """ text = '<table>' for group in args: diff --git a/tests/unit/misc/test_miscwidgets.py b/tests/unit/misc/test_miscwidgets.py index 6e1919ec3..3c5c1eef1 100644 --- a/tests/unit/misc/test_miscwidgets.py +++ b/tests/unit/misc/test_miscwidgets.py @@ -38,7 +38,7 @@ class TestCommandLineEdit: cmd_edit.set_prompt(':') qtbot.add_widget(cmd_edit) assert cmd_edit.text() == '' - yield cmd_edit + return cmd_edit def test_position(self, qtbot, cmd_edit): """Test cursor position based on the prompt.""" diff --git a/tests/unit/misc/userscripts/test_qute_lastpass.py b/tests/unit/misc/userscripts/test_qute_lastpass.py index 24272a048..ebd9a7591 100644 --- a/tests/unit/misc/userscripts/test_qute_lastpass.py +++ b/tests/unit/misc/userscripts/test_qute_lastpass.py @@ -122,7 +122,7 @@ class TestQuteLastPassComponents: subprocess_mock.assert_called_once_with( ['lpass', 'show', '-x', '-j', '-G', '\\bexample\\.com'], - stdout=ANY, stderr=ANY) + capture_output=True) def test_pass_returns_candidates(self, subprocess_mock): """Test if pass_ returns expected lpass site entry.""" @@ -263,7 +263,7 @@ class TestQuteLastPassMain: subprocess_mock.assert_has_calls([ call(['lpass', 'show', '-x', '-j', '-G', '\\bwww\\.example\\.com'], - stdout=ANY, stderr=ANY), + capture_output=True), call(['rofi', '-dmenu'], input=b'12345 | www.example.com | https://www.example.com | fake@fake.com\n23456 | Sites/www.example.com | https://www.example.com | john.doe@fake.com', stdout=ANY) @@ -328,13 +328,13 @@ class TestQuteLastPassMain: subprocess_mock.assert_has_calls([ call(['lpass', 'show', '-x', '-j', '-G', '\\bwww\\.example\\.com'], - stdout=ANY, stderr=ANY), + capture_output=True), call(['lpass', 'show', '-x', '-j', '-G', '\\bexample\\.com'], - stdout=ANY, stderr=ANY), + capture_output=True), call(['lpass', 'show', '-x', '-j', '-G', '\\bwwwexample'], - stdout=ANY, stderr=ANY), + capture_output=True), call(['lpass', 'show', '-x', '-j', '-G', '\\bexample'], - stdout=ANY, stderr=ANY), + capture_output=True), call(['rofi', '-dmenu'], input=b'12345 | www.example.com | https://www.example.com | fake@fake.com\n23456 | Sites/www.example.com | https://www.example.com | john.doe@fake.com\n345 | example.com | https://example.com | joe.doe@fake.com\n456 | Sites/example.com | http://example.com | jane.doe@fake.com', stdout=ANY) diff --git a/tests/unit/test_qutebrowser.py b/tests/unit/test_qutebrowser.py index d9275631d..36b4065a1 100644 --- a/tests/unit/test_qutebrowser.py +++ b/tests/unit/test_qutebrowser.py @@ -22,6 +22,8 @@ (Mainly commandline flag parsing) """ +import re + import pytest from qutebrowser import qutebrowser @@ -75,3 +77,61 @@ class TestJsonArgs: # pylint: disable=no-member assert args.debug assert not args.temp_basedir + + +class TestValidateUntrustedArgs: + + @pytest.mark.parametrize('args', [ + [], + [':nop'], + [':nop', '--untrusted-args'], + [':nop', '--debug', '--untrusted-args'], + [':nop', '--untrusted-args', 'foo'], + ['--debug', '--untrusted-args', 'foo'], + ['foo', '--untrusted-args', 'bar'], + ]) + def test_valid(self, args): + qutebrowser._validate_untrusted_args(args) + + @pytest.mark.parametrize('args, message', [ + ( + ['--untrusted-args', '--debug'], + "Found --debug after --untrusted-args, aborting.", + ), + ( + ['--untrusted-args', ':nop'], + "Found :nop after --untrusted-args, aborting.", + ), + ( + ['--debug', '--untrusted-args', '--debug'], + "Found --debug after --untrusted-args, aborting.", + ), + ( + [':nop', '--untrusted-args', '--debug'], + "Found --debug after --untrusted-args, aborting.", + ), + ( + [':nop', '--untrusted-args', ':nop'], + "Found :nop after --untrusted-args, aborting.", + ), + ( + [ + ':nop', + '--untrusted-args', + ':nop', + '--untrusted-args', + 'https://www.example.org', + ], + ( + "Found multiple arguments (:nop --untrusted-args " + "https://www.example.org) after --untrusted-args, aborting." + ) + ), + ( + ['--untrusted-args', 'okay1', 'okay2'], + "Found multiple arguments (okay1 okay2) after --untrusted-args, aborting.", + ), + ]) + def test_invalid(self, args, message): + with pytest.raises(SystemExit, match=re.escape(message)): + qutebrowser._validate_untrusted_args(args) diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py index 7682c1156..20938d6fb 100644 --- a/tests/unit/utils/test_log.py +++ b/tests/unit/utils/test_log.py @@ -27,7 +27,7 @@ import warnings import dataclasses import pytest -import _pytest.logging +import _pytest.logging # pylint: disable=import-private-name from PyQt5 import QtCore from qutebrowser import qutebrowser @@ -53,8 +53,7 @@ def restore_loggers(): saved_level_to_name = logging._levelToName.copy() logger_states = {} for name in saved_loggers: - logger_states[name] = getattr(saved_loggers[name], 'disabled', - None) + logger_states[name] = getattr(saved_loggers[name], 'disabled', None) finally: logging._releaseLock() @@ -86,9 +85,9 @@ def restore_loggers(): logger_dict = logging.getLogger().manager.loggerDict logger_dict.clear() logger_dict.update(saved_loggers) - for name in logger_states: - if logger_states[name] is not None: - saved_loggers[name].disabled = logger_states[name] + for name, state in logger_states.items(): + if state is not None: + saved_loggers[name].disabled = state finally: logging._releaseLock() @@ -237,6 +236,7 @@ class TestInitLog: """Tests for init_log.""" def _get_default_args(self): + # pylint: disable-next=unused-variable return argparse.Namespace(debug=True, loglevel='debug', color=True, loglines=10, logfilter=None, force_color=False, json_logging=False, @@ -349,7 +349,7 @@ class TestHideQtWarning: """Tests for hide_qt_warning/QtWarningFilter.""" - @pytest.fixture() + @pytest.fixture def qt_logger(self): return logging.getLogger('qt-tests') diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index 2d98feece..ad9adf032 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -51,9 +51,7 @@ else: test_file = None -# pylint: disable=bad-continuation -@pytest.mark.parametrize(['qversion', 'compiled', 'pyqt', 'version', 'exact', - 'expected'], [ +@pytest.mark.parametrize('qversion, compiled, pyqt, version, exact, expected', [ # equal versions ('5.14.0', None, None, '5.14.0', False, True), ('5.14.0', None, None, '5.14.0', True, True), # exact=True @@ -75,7 +73,6 @@ else: # dev suffix ('5.15.1', '5.15.1', '5.15.2.dev2009281246', '5.15.0', False, True), ]) -# pylint: enable=bad-continuation def test_version_check(monkeypatch, qversion, compiled, pyqt, version, exact, expected): """Test for version_check(). @@ -559,7 +556,8 @@ if test_file is not None: qiodev.mode = mode # Create empty TESTFN file because the Python tests try to unlink # it.after the test. - open(test_file.TESTFN, 'w', encoding='utf-8').close() + with open(test_file.TESTFN, 'w', encoding='utf-8'): + pass return qiodev class PyAutoFileTests(PyIODeviceTestMixin, test_file.AutoFileTests, @@ -742,7 +740,7 @@ class TestPyQIODevice: with pytest.raises(io.UnsupportedOperation): pyqiodev.seek(0, whence) - @pytest.mark.flaky() + @pytest.mark.flaky def test_qprocess(self, py_proc): """Test PyQIODevice with a QProcess which is non-sequential. diff --git a/tests/unit/utils/test_resources.py b/tests/unit/utils/test_resources.py index 738fadd37..6a11ff588 100644 --- a/tests/unit/utils/test_resources.py +++ b/tests/unit/utils/test_resources.py @@ -65,7 +65,7 @@ class TestReadFile: 'qutebrowser/html/unrelatedhtml', ] - yield zipfile.Path(zip_path) / 'qutebrowser' + return zipfile.Path(zip_path) / 'qutebrowser' @pytest.fixture(params=['pathlib', 'zipfile']) def resource_root(self, request): diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index e05b5d31f..0ca635ae2 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -31,7 +31,7 @@ import subprocess from PyQt5.QtCore import QStandardPaths import pytest -from qutebrowser.utils import standarddir, utils, qtutils +from qutebrowser.utils import standarddir, utils, qtutils, version # Use a different application name for tests to make sure we don't change real @@ -399,12 +399,18 @@ class TestSystemData: """Test system data path.""" @pytest.mark.linux - def test_system_datadir_exist_linux(self, monkeypatch, tmpdir): + @pytest.mark.parametrize("is_flatpak, expected", [ + (True, "/app/share/qute_test"), + (False, "/usr/share/qute_test"), + ]) + def test_system_datadir_exist_linux(self, monkeypatch, tmpdir, + is_flatpak, expected): """Test that /usr/share/qute_test is used if path exists.""" monkeypatch.setenv('XDG_DATA_HOME', str(tmpdir)) monkeypatch.setattr(os.path, 'exists', lambda path: True) + monkeypatch.setattr(version, 'is_flatpak', lambda: is_flatpak) standarddir._init_data(args=None) - assert standarddir.data(system=True) == "/usr/share/qute_test" + assert standarddir.data(system=True) == expected @pytest.mark.linux def test_system_datadir_not_exist_linux(self, monkeypatch, tmpdir, @@ -492,7 +498,7 @@ def test_no_qapplication(qapp, tmpdir, monkeypatch): monkeypatch.setenv('HOME', str(home_dir)) proc = subprocess.run([sys.executable, str(pyfile)] + sys.path, - universal_newlines=True, + text=True, check=True, stdout=subprocess.PIPE) sub_locations = json.loads(proc.stdout) diff --git a/tests/unit/utils/test_urlmatch.py b/tests/unit/utils/test_urlmatch.py index 35ccc94fe..caf52c76d 100644 --- a/tests/unit/utils/test_urlmatch.py +++ b/tests/unit/utils/test_urlmatch.py @@ -37,24 +37,30 @@ from PyQt5.QtCore import QUrl from qutebrowser.utils import urlmatch +# pylint: disable=line-too-long + @pytest.mark.parametrize('pattern, error', [ ### Chromium: kMissingSchemeSeparator ## TEST(ExtensionURLPatternTest, ParseInvalid) # ("http", "No scheme given"), - ("http:", "Invalid port: Port is empty"), - ("http:/", "Invalid port: Port is empty"), - ("about://", "Pattern without path"), - ("http:/bar", "Invalid port: Port is empty"), + pytest.param("http:", "Invalid port: Port is empty", id='scheme-no-slash'), + pytest.param("http:/", "Invalid port: Port is empty", id='scheme-single-slash'), + pytest.param("about://", "Pattern without path", id='scheme-no-path'), + pytest.param( + "http:/bar", + "Invalid port: Port is empty", + id='scheme-single-slash-path', + ), ### Chromium: kEmptyHost ## TEST(ExtensionURLPatternTest, ParseInvalid) - ("http://", "Pattern without host"), - ("http:///", "Pattern without host"), - ("http://:1234/", "Pattern without host"), - ("http://*./", "Pattern without host"), + pytest.param("http://", "Pattern without host", id='host-double-slash'), + pytest.param("http:///", "Pattern without host", id='host-triple-slash'), + pytest.param("http://:1234/", "Pattern without host", id='host-port'), + pytest.param("http://*./", "Pattern without host", id='host-pattern'), ## TEST(ExtensionURLPatternTest, IPv6Patterns) - ("http://[]:8888/*", "Pattern without host"), + pytest.param("http://[]:8888/*", "Pattern without host", id='host-ipv6'), ### Chromium: kEmptyPath ## TEST(ExtensionURLPatternTest, ParseInvalid) @@ -63,53 +69,132 @@ from qutebrowser.utils import urlmatch ### Chromium: kInvalidHost ## TEST(ExtensionURLPatternTest, ParseInvalid) - ("http://\0www/", "May not contain NUL byte"), + pytest.param("http://\0www/", "May not contain NUL byte", id='host-nul'), ## TEST(ExtensionURLPatternTest, IPv6Patterns) # No closing bracket (`]`). - ("http://[2607:f8b0:4005:805::200e/*", "Invalid IPv6 URL"), + pytest.param( + "http://[2607:f8b0:4005:805::200e/*", + "Invalid IPv6 URL", + id='host-ipv6-no-closing', + ), # Two closing brackets (`]]`). - pytest.param("http://[2607:f8b0:4005:805::200e]]/*", "Invalid IPv6 URL", marks=pytest.mark.xfail(reason="https://bugs.python.org/issue34360")), + pytest.param( + "http://[2607:f8b0:4005:805::200e]]/*", + "Invalid IPv6 URL", + marks=pytest.mark.xfail(reason="https://bugs.python.org/issue34360"), + id='host-ipv6-two-closing', + ), # Two open brackets (`[[`). - ("http://[[2607:f8b0:4005:805::200e]/*", r"""Expected '\]' to match '\[' in hostname; source was "\[2607:f8b0:4005:805::200e"; host = """""), + pytest.param( + "http://[[2607:f8b0:4005:805::200e]/*", + r"""Expected '\]' to match '\[' in hostname; source was "\[2607:f8b0:4005:805::200e"; host = """"", + id='host-ipv6-two-open', + ), # Too few colons in the last chunk. - ("http://[2607:f8b0:4005:805:200e]/*", 'Invalid IPv6 address; source was "2607:f8b0:4005:805:200e"; host = ""'), + pytest.param( + "http://[2607:f8b0:4005:805:200e]/*", + 'Invalid IPv6 address; source was "2607:f8b0:4005:805:200e"; host = ""', + id='host-ipv6-colons', + ), # Non-hex piece. - ("http://[2607:f8b0:4005:805:200e:12:bogus]/*", 'Invalid IPv6 address; source was "2607:f8b0:4005:805:200e:12:bogus"; host = ""'), + pytest.param( + "http://[2607:f8b0:4005:805:200e:12:bogus]/*", + 'Invalid IPv6 address; source was "2607:f8b0:4005:805:200e:12:bogus"; host = ""', + id='host-ipv6-non-hex', + ), ### Chromium: kInvalidHostWildcard ## TEST(ExtensionURLPatternTest, ParseInvalid) - ("http://*foo/bar", "Invalid host wildcard"), - ("http://foo.*.bar/baz", "Invalid host wildcard"), - ("http://fo.*.ba:123/baz", "Invalid host wildcard"), - ("http://foo.*/bar", "Invalid host wildcard"), + pytest.param("http://*foo/bar", "Invalid host wildcard", id='host-wildcard-no-dot'), + pytest.param( + "http://foo.*.bar/baz", + "Invalid host wildcard", + id='host-wildcard-middle', + ), + pytest.param( + "http://fo.*.ba:123/baz", + "Invalid host wildcard", + id='host-wildcard-middle-port', + ), + pytest.param("http://foo.*/bar", "Invalid host wildcard", id='host-wildcard-end'), ### Chromium: kInvalidPort ## TEST(ExtensionURLPatternTest, Ports) - ("http://foo:/", "Invalid port: Port is empty"), - ("http://*.foo:/", "Invalid port: Port is empty"), - ("http://foo:com/", "Invalid port: .* 'com'"), - ("http://foo:123456/", "Invalid port: Port out of range 0-65535"), - ("http://foo:80:80/monkey", "Invalid port: .* '80:80'"), - ("chrome://foo:1234/bar", "Ports are unsupported with chrome scheme"), + pytest.param("http://foo:/", "Invalid port: Port is empty", id='port-empty'), + pytest.param( + "http://*.foo:/", + "Invalid port: Port is empty", + id='port-empty-wildcard', + ), + pytest.param("http://foo:com/", "Invalid port: .* 'com'", id='port-alpha'), + pytest.param( + "http://foo:123456/", + "Invalid port: Port out of range 0-65535", + id='port-range', + ), + pytest.param( + "http://foo:80:80/monkey", + "Invalid port: .* '80:80'", + id='port-double', + ), + pytest.param( + "chrome://foo:1234/bar", + "Ports are unsupported with chrome scheme", + id='port-chrome', + ), # No port specified, but port separator. - ("http://[2607:f8b0:4005:805::200e]:/*", "Invalid port: Port is empty"), + pytest.param( + "http://[2607:f8b0:4005:805::200e]:/*", + "Invalid port: Port is empty", + id='port-empty-ipv6', + ), ### Additional tests - ("http://[", "Invalid IPv6 URL"), - ("http://[fc2e::bb88::edac]", 'Invalid IPv6 address; source was "fc2e::bb88::edac"; host = ""'), - ("http://[fc2e:0e35:bb88::edac:fc2e:0e35:bb88:edac]", 'Invalid IPv6 address; source was "fc2e:0e35:bb88::edac:fc2e:0e35:bb88:edac"; host = ""'), - ("http://[fc2e:0e35:bb88:af:edac:fc2e:0e35:bb88:edac]", 'Invalid IPv6 address; source was "fc2e:0e35:bb88:af:edac:fc2e:0e35:bb88:edac"; host = ""'), - ("http://[127.0.0.1:fc2e::bb88:edac]", r'Invalid IPv6 address; source was "127\.0\.0\.1:fc2e::bb88:edac'), - ("http://[fc2e::bb88", "Invalid IPv6 URL"), - ("http://[fc2e:bb88:edac]", 'Invalid IPv6 address; source was "fc2e:bb88:edac"; host = ""'), - ("http://[fc2e:bb88:edac::z]", 'Invalid IPv6 address; source was "fc2e:bb88:edac::z"; host = ""'), - ("http://[fc2e:bb88:edac::2]:2a2", "Invalid port: .* '2a2'"), - ("://", "Missing scheme"), + pytest.param("http://[", "Invalid IPv6 URL", id='ipv6-single-open'), + pytest.param( + "http://[fc2e::bb88::edac]", + 'Invalid IPv6 address; source was "fc2e::bb88::edac"; host = ""', + id='ipv6-double-double', + ), + pytest.param( + "http://[fc2e:0e35:bb88::edac:fc2e:0e35:bb88:edac]", + 'Invalid IPv6 address; source was "fc2e:0e35:bb88::edac:fc2e:0e35:bb88:edac"; host = ""', + id='ipv6-long-double', + ), + pytest.param( + "http://[fc2e:0e35:bb88:af:edac:fc2e:0e35:bb88:edac]", + 'Invalid IPv6 address; source was "fc2e:0e35:bb88:af:edac:fc2e:0e35:bb88:edac"; host = ""', + id='ipv6-long', + ), + pytest.param( + "http://[127.0.0.1:fc2e::bb88:edac]", + r'Invalid IPv6 address; source was "127\.0\.0\.1:fc2e::bb88:edac', + id='ipv6-ipv4', + ), + pytest.param("http://[fc2e::bb88", "Invalid IPv6 URL", id='ipv6-trailing'), + pytest.param( + "http://[fc2e:bb88:edac]", + 'Invalid IPv6 address; source was "fc2e:bb88:edac"; host = ""', + id='ipv6-short', + ), + pytest.param( + "http://[fc2e:bb88:edac::z]", + 'Invalid IPv6 address; source was "fc2e:bb88:edac::z"; host = ""', + id='ipv6-z', + ), + pytest.param( + "http://[fc2e:bb88:edac::2]:2a2", + "Invalid port: .* '2a2'", + id='ipv6-port', + ), + pytest.param("://", "Missing scheme", id='scheme-naked'), ]) def test_invalid_patterns(pattern, error): with pytest.raises(urlmatch.ParseError, match=error): urlmatch.UrlPattern(pattern) +# pylint: enable=line-too-long + @pytest.mark.parametrize('host', ['.', ' ', ' .', '. ', '. .', '. . .', ' . ']) def test_whitespace_hosts(host): diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 97ff268ca..e5773e25e 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -334,7 +334,6 @@ def test_get_search_url_open_base_url(config_stub, url, host): Args: url: The "URL" to enter. host: The expected search machine host. - query: The expected search query. """ config_stub.val.url.open_base_url = True url = urlutils._get_search_url(url) diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index 57adc883c..c833aa677 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -30,7 +30,7 @@ import shlex import math import operator -from PyQt5.QtCore import QUrl, QRect +from PyQt5.QtCore import QUrl, QRect, QPoint from PyQt5.QtGui import QClipboard import pytest import hypothesis @@ -603,6 +603,15 @@ class TestSanitizeFilename: LONG_EXTENSION = (LONG_FILENAME.replace("filename", ".extension") .replace(".txt", "")) + # first four-byte unicode char + U10K = "\U00010000" + + LONG_4BYTE = U10K * 64 + LONG_4BYTE_SHORTENED = U10K * 60 + + LONG_4BYTE_EXT = f"{U10K * 8}.{U10K * 64}" + LONG_4BYTE_EXT_SHORTENED = f"{U10K}.{U10K * 59}" + @pytest.mark.parametrize('inp, expected', [ pytest.param('normal.txt', 'normal.txt', marks=pytest.mark.fake_os('windows')), @@ -629,6 +638,14 @@ class TestSanitizeFilename: LONG_EXTENSION.replace("this is a very long .extension", "this .extension"), ), + ( + LONG_4BYTE, + LONG_4BYTE_SHORTENED, + ), + ( + LONG_4BYTE_EXT, + LONG_4BYTE_EXT_SHORTENED, + ) ]) @pytest.mark.linux def test_shorten(self, inp, expected): @@ -809,8 +826,11 @@ class TestYaml: assert utils.yaml_load("[1, 2]") == [1, 2] def test_load_float_bug(self): - with pytest.raises(yaml.YAMLError): + try: utils.yaml_load("._") + except yaml.YAMLError: + # Either no exception or YAMLError, not ValueError + pass def test_load_file(self, tmp_path): tmpfile = tmp_path / 'foo.yml' @@ -928,7 +948,7 @@ def test_parse_duration_hypothesis(duration): @pytest.mark.parametrize('mimetype, extension', [ ('application/pdf', '.pdf'), # handled by Python - ('text/plain', '.txt'), # wrong in Python 3.6, overridden + ('text/plain', '.txt'), # was wrong in Python 3.6, handled now ('application/manifest+json', '.webmanifest'), # newer ('text/xul', '.xul'), # strict=False ('doesnot/exist', None), @@ -1023,3 +1043,44 @@ class TestParseRect: utils.parse_rect(s) except ValueError as e: print(e) + + +class TestParsePoint: + + @pytest.mark.parametrize('value, expected', [ + ('1,1', QPoint(1, 1)), + ('123,789', QPoint(123, 789)), + ('-123,-789', QPoint(-123, -789)), + ]) + def test_valid(self, value, expected): + assert utils.parse_point(value) == expected + + @pytest.mark.parametrize('value, message', [ + ('1x1', "String 1x1 does not match X,Y"), + ('1e0,1', "String 1e0,1 does not match X,Y"), + ('a,1', "String a,1 does not match X,Y"), + ('¹,1', "String ¹,1 does not match X,Y"), + ('1,,1', "String 1,,1 does not match X,Y"), + ('1', "String 1 does not match X,Y"), + ]) + def test_invalid(self, value, message): + with pytest.raises(ValueError) as excinfo: + utils.parse_point(value) + assert str(excinfo.value) == message + + @hypothesis.given(strategies.text()) + def test_hypothesis_text(self, s): + try: + utils.parse_point(s) + except ValueError as e: + print(e) + + @hypothesis.given(strategies.tuples( + strategies.integers(), + strategies.integers(), + ).map(lambda t: ",".join(map(str, t)))) + def test_hypothesis_sophisticated(self, s): + try: + utils.parse_point(s) + except ValueError as e: + print(e) diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 6c57cb3d3..64df0ece2 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -484,17 +484,20 @@ class TestGitStrSubprocess: @needs_git def test_real_git(self, git_repo): """Test with a real git repository.""" - branch_name = subprocess.run( - ['git', 'config', 'init.defaultBranch'], - check=False, - stdout=subprocess.PIPE, - encoding='utf-8', - ).stdout.strip() - if not branch_name: - branch_name = 'master' + def _get_git_setting(name, default): + return subprocess.run( + ['git', 'config', '--default', default, name], + check=True, + stdout=subprocess.PIPE, + encoding='utf-8', + ).stdout.strip() ret = version._git_str_subprocess(str(git_repo)) - assert ret == f'6e4b65a on {branch_name} (1970-01-01 01:00:00 +0100)' + branch_name = _get_git_setting('init.defaultBranch', 'master') + abbrev_length = int(_get_git_setting('core.abbrev', '7')) + expected_sha = '6e4b65a529c0ab78fb370c1527d5809f7436b8f3'[:abbrev_length] + + assert ret == f'{expected_sha} on {branch_name} (1970-01-01 01:00:00 +0100)' def test_missing_dir(self, tmp_path): """Test with a directory which doesn't exist.""" @@ -718,7 +721,7 @@ class TestModuleVersions: Args: attribute: The name of the version attribute. - expected: The expected return value. + expected_modules: The expected modules with that attribute. """ import_fake.version_attribute = attribute @@ -1460,7 +1463,11 @@ def test_uptime(monkeypatch, qapp): monkeypatch.setattr(qapp, "launch_time", launch_time, raising=False) class FakeDateTime(datetime.datetime): - now = lambda x=datetime.datetime(1, 1, 1, 1, 1, 1, 2): x + + @classmethod + def now(cls, tz=None): + return datetime.datetime(1, 1, 1, 1, 1, 1, 2) + monkeypatch.setattr(datetime, 'datetime', FakeDateTime) uptime_delta = version._uptime() @@ -13,17 +13,16 @@ minversion = 3.15 setenv = PYTEST_QT_API=pyqt5 pyqt{,512,513,514,515,5150}: LINK_PYQT_SKIP=true - pyqt{,512,513,514,515,5150}: QUTE_BDD_WEBENGINE=true cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report= -passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI XDG_* QUTE_* DOCKER QT_QUICK_BACKEND PY_COLORS DBUS_SESSION_BUS_ADDRESS +passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI XDG_* QUTE_* DOCKER QT_QUICK_BACKEND FORCE_COLOR DBUS_SESSION_BUS_ADDRESS basepython = py: {env:PYTHON:python3} py3: {env:PYTHON:python3} - py36: {env:PYTHON:python3.6} py37: {env:PYTHON:python3.7} py38: {env:PYTHON:python3.8} py39: {env:PYTHON:python3.9} py310: {env:PYTHON:python3.10} + py311: {env:PYTHON:python3.11} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-tests.txt @@ -42,7 +41,6 @@ commands = basepython = {env:PYTHON:python3} setenv = PYTEST_QT_API=pyqt5 - QUTE_BDD_WEBENGINE=true pip_pre = true deps = -r{toxinidir}/misc/requirements/requirements-tests-bleeding.txt commands_pre = pip install --index-url https://www.riverbankcomputing.com/pypi/simple/ --pre --upgrade PyQt5 PyQtWebEngine @@ -80,7 +78,7 @@ commands = {[testenv:vulture]commands} [testenv:pylint] -basepython = {env:PYTHON:python3.8} +basepython = {env:PYTHON:python3} ignore_errors = true passenv = deps = @@ -160,7 +158,7 @@ passenv = APPDATA HOME PYINSTALLER_DEBUG deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-pyinstaller.txt - -r{toxinidir}/misc/requirements/requirements-pyqt-pyinstaller.txt + -r{toxinidir}/misc/requirements/requirements-pyqt.txt commands = {envbindir}/pyinstaller --noconfirm misc/qutebrowser.spec |