diff options
240 files changed, 4965 insertions, 4770 deletions
@@ -1,7 +1,7 @@ .DS_Store MANIFEST */tmp -/venv +/venv* *.py[cdo] *.swp *.swo @@ -9,6 +9,7 @@ MANIFEST .coverage .idea .cache/ +.tox/ build/ # UI @@ -16,3 +17,5 @@ build/ node_modules bower_components *.map +sslkeylogfile.log +.tox/ diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..2339c8bf --- /dev/null +++ b/.python-version @@ -0,0 +1,2 @@ +2.7.11 +3.5.1 diff --git a/.travis.yml b/.travis.yml index 7d3fbee8..5ec8b3bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,9 @@ matrix: git: depth: 9999999 - python: 3.5 - env: SCOPE="netlib ./test/mitmproxy/script" + env: SCOPE="netlib ./test/mitmproxy/script ./test/pathod/test_utils.py ./test/pathod/test_log.py ./test/pathod/test_language_generators.py" - python: 3.5 - env: SCOPE="netlib ./test/mitmproxy/script" NO_ALPN=1 + env: SCOPE="netlib ./test/mitmproxy/script ./test/pathod/test_utils.py ./test/pathod/test_log.py ./test/pathod/test_language_generators.py" NO_ALPN=1 - python: 2.7 env: DOCS=1 script: 'cd docs && make html' @@ -46,6 +46,7 @@ install: before_script: - "openssl version -a" - "python -c \"from OpenSSL import SSL; print(SSL.SSLeay_version(SSL.SSLEAY_VERSION))\"" + - "flake8 --count mitmproxy netlib pathod examples test" script: - "py.test --timeout 60 --cov netlib --cov mitmproxy --cov pathod ./test/$SCOPE" @@ -61,16 +62,12 @@ after_success: fi notifications: - irc: - channels: - - "irc.oftc.net#mitmproxy" - on_success: change - on_failure: always slack: - rooms: - - mitmproxy:YaDGC9Gt9TEM7o8zkC2OLNsu#ci - on_success: always - on_failure: always + -rooms: + mitmproxy:YaDGC9Gt9TEM7o8zkC2OLNsu + on_success: change + on_failure: change + on_start: never cache: directories: @@ -1,21 +1,26 @@ mitmproxy ^^^^^^^^^ -|travis| |coveralls| |downloads| |latest_release| |python_versions| +|travis| |coveralls| |latest_release| |python_versions| -This repository contains the **mitmproxy** and **pathod** projects, as well as their shared networking library, **netlib**. +This repository contains the **mitmproxy** and **pathod** projects, as well as +their shared networking library, **netlib**. -``mitmproxy`` is an interactive, SSL-capable intercepting proxy with a console interface. +``mitmproxy`` is an interactive, SSL-capable intercepting proxy with a console +interface. ``mitmdump`` is the command-line version of mitmproxy. Think tcpdump for HTTP. -``pathoc`` and ``pathod`` are perverse HTTP client and server applications designed to let you craft almost any conceivable HTTP request, including ones that creatively violate the standards. +``pathoc`` and ``pathod`` are perverse HTTP client and server applications +designed to let you craft almost any conceivable HTTP request, including ones +that creatively violate the standards. Documentation & Help -------------------- -Documentation, tutorials and precompiled binaries can be found on the mitmproxy and pathod websites. +Documentation, tutorials and precompiled binaries can be found on the mitmproxy +and pathod websites. |mitmproxy_site| |pathod_site| @@ -28,12 +33,19 @@ You can join our developer chat on Slack. |slack| +Installation +------------ + +The installation instructions are `here <http://docs.mitmproxy.org/en/stable/install.html>`_. +If you want to contribute changes, keep on reading. + + Hacking ------- To get started hacking on mitmproxy, make sure you have Python_ 2.7.x. with -virtualenv_ installed (you can find installation instructions for virtualenv here_). -Then do the following: +virtualenv_ installed (you can find installation instructions for virtualenv +here_). Then do the following: .. code-block:: text @@ -42,10 +54,11 @@ Then do the following: ./dev.sh -The *dev* script will create a virtualenv environment in a directory called "venv", -and install all mandatory and optional dependencies into it. -The primary mitmproxy components - mitmproxy, netlib and pathod - are installed as "editable", -so any changes to the source in the repository will be reflected live in the virtualenv. +The *dev* script will create a virtualenv environment in a directory called +"venv", and install all mandatory and optional dependencies into it. The +primary mitmproxy components - mitmproxy, netlib and pathod - are installed as +"editable", so any changes to the source in the repository will be reflected +live in the virtualenv. To confirm that you're up and running, activate the virtualenv, and run the mitmproxy test suite: @@ -56,9 +69,9 @@ mitmproxy test suite: py.test Note that the main executables for the project - ``mitmdump``, ``mitmproxy``, -``mitmweb``, ``pathod``, and ``pathoc`` - are all created within the virtualenv. After activating the -virtualenv, they will be on your $PATH, and you can run them like any other -command: +``mitmweb``, ``pathod``, and ``pathoc`` - are all created within the +virtualenv. After activating the virtualenv, they will be on your $PATH, and +you can run them like any other command: .. code-block:: text @@ -85,9 +98,9 @@ suite. The project tries to maintain 100% test coverage. Documentation ------------- -The mitmproxy documentation is build using Sphinx_, which is installed automatically if you set up a development -environment as described above. -After installation, you can render the documentation like this: +The mitmproxy documentation is build using Sphinx_, which is installed +automatically if you set up a development environment as described above. After +installation, you can render the documentation like this: .. code-block:: text @@ -99,6 +112,13 @@ After installation, you can render the documentation like this: The last command invokes `sphinx-autobuild`_, which watches the Sphinx directory and rebuilds the documentation when a change is detected. +Style +----- + +Keeping to a consistent code style throughout the project makes it easier to +contribute and collaborate. Please stick to the guidelines in +`PEP8`_ and the `Google Style Guide`_ unless there's a very +good reason not to. .. |mitmproxy_site| image:: https://shields.mitmproxy.org/api/https%3A%2F%2F-mitmproxy.org-blue.svg @@ -125,10 +145,6 @@ the documentation when a change is detected. :target: https://coveralls.io/r/mitmproxy/mitmproxy :alt: Coverage Status -.. |downloads| image:: https://shields.mitmproxy.org/pypi/dm/mitmproxy.svg?color=orange - :target: https://pypi.python.org/pypi/mitmproxy - :alt: Downloads - .. |latest_release| image:: https://shields.mitmproxy.org/pypi/v/mitmproxy.svg :target: https://pypi.python.org/pypi/mitmproxy :alt: Latest Version @@ -145,3 +161,5 @@ the documentation when a change is detected. .. _Sphinx: http://sphinx-doc.org/ .. _sphinx-autobuild: https://pypi.python.org/pypi/sphinx-autobuild .. _issue_tracker: https://github.com/mitmproxy/mitmproxy/issues +.. _PEP8: https://www.python.org/dev/peps/pep-0008 +.. _Google Style Guide: https://google.github.io/styleguide/pyguide.html @@ -1,13 +1,17 @@ -#!/bin/bash +#!/bin/sh set -e -VENV=./venv +set -x -python -m virtualenv $VENV --always-copy -. $VENV/bin/activate -pip install -U pip setuptools -pip install -r requirements.txt +PYVERSION=$1 +VENV="venv$1" + +echo "Creating dev environment in $VENV using Python $PYVERSION" + +python$PYVERSION -m virtualenv "$VENV" --always-copy +. "$VENV/bin/activate" +pip$PYVERSION install -U pip setuptools +pip$PYVERSION install -r requirements.txt echo "" -echo "* Created virtualenv environment in $VENV." -echo "* Installed all dependencies into the virtualenv." -echo "* You can now activate the virtualenv: \`. $VENV/bin/activate\`" +echo "* Virtualenv created in $VENV and all dependencies installed." +echo "* You can now activate the $(python --version) virtualenv with this command: \`. $VENV/bin/activate\`" diff --git a/examples/change_upstream_proxy.py b/examples/change_upstream_proxy.py index 9c454897..34a6eece 100644 --- a/examples/change_upstream_proxy.py +++ b/examples/change_upstream_proxy.py @@ -21,4 +21,4 @@ def request(context, flow): return address = proxy_address(flow) if flow.live: - flow.live.change_upstream_proxy_server(address)
\ No newline at end of file + flow.live.change_upstream_proxy_server(address) diff --git a/examples/custom_contentviews.py b/examples/custom_contentviews.py index f3b7317f..034f356c 100644 --- a/examples/custom_contentviews.py +++ b/examples/custom_contentviews.py @@ -1,7 +1,8 @@ import string import lxml.html import lxml.etree -from mitmproxy import utils, contentviews +from mitmproxy import contentviews +from netlib import strutils class ViewPigLatin(contentviews.View): @@ -10,7 +11,7 @@ class ViewPigLatin(contentviews.View): content_types = ["text/html"] def __call__(self, data, **metadata): - if utils.isXML(data): + if strutils.isXML(data): parser = lxml.etree.HTMLParser( strip_cdata=True, remove_blank_text=True @@ -23,7 +24,8 @@ class ViewPigLatin(contentviews.View): ret = '' for word in words: idx = -1 - while word[idx] in string.punctuation and (idx * -1) != len(word): idx -= 1 + while word[idx] in string.punctuation and (idx * -1) != len(word): + idx -= 1 if word[0].lower() in 'aeiou': if idx == -1: ret += word[0:] + "hay" diff --git a/examples/dns_spoofing.py b/examples/dns_spoofing.py index 7eb79695..8d715f33 100644 --- a/examples/dns_spoofing.py +++ b/examples/dns_spoofing.py @@ -22,7 +22,6 @@ Usage: """ import re - # This regex extracts splits the host header into host and port. # Handles the edge case of IPv6 addresses containing colons. # https://bugzilla.mozilla.org/show_bug.cgi?id=45891 @@ -47,4 +46,4 @@ def request(context, flow): port = int(m.group("port")) flow.request.host = sni or host_header - flow.request.port = port
\ No newline at end of file + flow.request.port = port diff --git a/examples/flowbasic b/examples/flowbasic index 4a87b86a..74af4e08 100644..100755 --- a/examples/flowbasic +++ b/examples/flowbasic @@ -8,7 +8,7 @@ Note that request and response messages are not automatically replied to, so we need to implement handlers to do this. """ -from mitmproxy import flow +from mitmproxy import flow, controller from mitmproxy.proxy import ProxyServer, ProxyConfig @@ -19,18 +19,15 @@ class MyMaster(flow.FlowMaster): except KeyboardInterrupt: self.shutdown() - def handle_request(self, f): - f = flow.FlowMaster.handle_request(self, f) - if f: - f.reply() - return f + @controller.handler + def request(self, f): + f = flow.FlowMaster.request(self, f) + print(f) - def handle_response(self, f): - f = flow.FlowMaster.handle_response(self, f) - if f: - f.reply() + @controller.handler + def response(self, f): + f = flow.FlowMaster.response(self, f) print(f) - return f config = ProxyConfig( diff --git a/examples/har_extractor.py b/examples/har_extractor.py index 371e2282..6806989d 100644 --- a/examples/har_extractor.py +++ b/examples/har_extractor.py @@ -162,8 +162,11 @@ def response(context, flow): # If the current url is in the page list of context.HARLog or # does not have a referrer, we add it as a new pages object. - if (flow.request.url in context.HARLog.get_page_list() or - flow.request.headers.get('Referer') is None): + is_new_page = ( + flow.request.url in context.HARLog.get_page_list() or + flow.request.headers.get('Referer') is None + ) + if is_new_page: page_id = context.HARLog.create_page_id() context.HARLog.add( HAR.pages({ diff --git a/examples/mitmproxywrapper.py b/examples/mitmproxywrapper.py index 7ea10715..6841d05f 100644 --- a/examples/mitmproxywrapper.py +++ b/examples/mitmproxywrapper.py @@ -16,7 +16,6 @@ import sys class Wrapper(object): - def __init__(self, port, extra_arguments=None): self.port = port self.extra_arguments = extra_arguments @@ -142,7 +141,7 @@ class Wrapper(object): '--toggle', action='store_true', help='just toggle the proxy configuration') -# parser.add_argument('--honeyproxy', action='store_true', help='run honeyproxy instead of mitmproxy') + # parser.add_argument('--honeyproxy', action='store_true', help='run honeyproxy instead of mitmproxy') parser.add_argument( '-p', '--port', @@ -155,8 +154,8 @@ class Wrapper(object): if args.toggle: wrapper.toggle_proxy() -# elif args.honeyproxy: -# wrapper.wrap_honeyproxy() + # elif args.honeyproxy: + # wrapper.wrap_honeyproxy() else: wrapper.wrap_mitmproxy() diff --git a/examples/pathod/test_setup.py b/examples/pathod/test_setup.py index 5dbc456d..32fcb214 100644 --- a/examples/pathod/test_setup.py +++ b/examples/pathod/test_setup.py @@ -3,7 +3,6 @@ from pathod import test class Test: - """ Testing the requests module with a pathod instance started for diff --git a/examples/pathod/test_setupall.py b/examples/pathod/test_setupall.py index cb84b7b2..cc0ec2e4 100644 --- a/examples/pathod/test_setupall.py +++ b/examples/pathod/test_setupall.py @@ -3,12 +3,12 @@ from pathod import test class Test: - """ Testing the requests module with a single pathod instance started for the test suite. """ + @classmethod def setup_class(cls): cls.d = test.Daemon() diff --git a/examples/redirect_requests.py b/examples/redirect_requests.py index c0a0ccba..3ff8f9e4 100644 --- a/examples/redirect_requests.py +++ b/examples/redirect_requests.py @@ -4,6 +4,7 @@ This example shows two ways to redirect flows to other destinations. from mitmproxy.models import HTTPResponse from netlib.http import Headers + def request(context, flow): # pretty_host takes the "Host" header of the request into account, # which is useful in transparent mode where we usually only have the IP diff --git a/examples/sslstrip.py b/examples/sslstrip.py index 369427a2..1bc89946 100644 --- a/examples/sslstrip.py +++ b/examples/sslstrip.py @@ -2,39 +2,39 @@ from netlib.http import decoded import re from six.moves import urllib -def start(context, argv) : - #set of SSL/TLS capable hosts +def start(context, argv): + # set of SSL/TLS capable hosts context.secure_hosts = set() -def request(context, flow) : +def request(context, flow): flow.request.headers.pop('If-Modified-Since', None) flow.request.headers.pop('Cache-Control', None) - #proxy connections to SSL-enabled hosts - if flow.request.pretty_host in context.secure_hosts : + # proxy connections to SSL-enabled hosts + if flow.request.pretty_host in context.secure_hosts: flow.request.scheme = 'https' flow.request.port = 443 -def response(context, flow) : - with decoded(flow.response) : +def response(context, flow): + with decoded(flow.response): flow.request.headers.pop('Strict-Transport-Security', None) flow.request.headers.pop('Public-Key-Pins', None) - #strip links in response body + # strip links in response body flow.response.content = flow.response.content.replace('https://', 'http://') - #strip links in 'Location' header - if flow.response.headers.get('Location','').startswith('https://'): + # strip links in 'Location' header + if flow.response.headers.get('Location', '').startswith('https://'): location = flow.response.headers['Location'] hostname = urllib.parse.urlparse(location).hostname if hostname: context.secure_hosts.add(hostname) flow.response.headers['Location'] = location.replace('https://', 'http://', 1) - #strip secure flag from 'Set-Cookie' headers + # strip secure flag from 'Set-Cookie' headers cookies = flow.response.headers.get_all('Set-Cookie') cookies = [re.sub(r';\s*secure\s*', '', s) for s in cookies] flow.response.headers.set_all('Set-Cookie', cookies) diff --git a/examples/stickycookies b/examples/stickycookies index 8f11de8d..43e5371d 100644..100755 --- a/examples/stickycookies +++ b/examples/stickycookies @@ -21,19 +21,19 @@ class StickyMaster(controller.Master): except KeyboardInterrupt: self.shutdown() - def handle_request(self, flow): + @controller.handler + def request(self, flow): hid = (flow.request.host, flow.request.port) if "cookie" in flow.request.headers: self.stickyhosts[hid] = flow.request.headers.get_all("cookie") elif hid in self.stickyhosts: flow.request.headers.set_all("cookie", self.stickyhosts[hid]) - flow.reply() - def handle_response(self, flow): + @controller.handler + def response(self, flow): hid = (flow.request.host, flow.request.port) if "set-cookie" in flow.response.headers: self.stickyhosts[hid] = flow.response.headers.get_all("set-cookie") - flow.reply() config = proxy.ProxyConfig(port=8080) diff --git a/examples/tcp_message.py b/examples/tcp_message.py index c63368e4..2c210618 100644 --- a/examples/tcp_message.py +++ b/examples/tcp_message.py @@ -8,7 +8,8 @@ tcp_message Inline Script Hook API Demonstration example cmdline invocation: mitmdump -T --host --tcp ".*" -q -s examples/tcp_message.py ''' -from netlib.utils import clean_bin +from netlib import strutils + def tcp_message(ctx, tcp_msg): modified_msg = tcp_msg.message.replace("foo", "bar") @@ -21,4 +22,4 @@ def tcp_message(ctx, tcp_msg): "client" if tcp_msg.sender == tcp_msg.client_conn else "server", tcp_msg.sender.address, "server" if tcp_msg.receiver == tcp_msg.server_conn else "client", - tcp_msg.receiver.address, clean_bin(tcp_msg.message))) + tcp_msg.receiver.address, strutils.clean_bin(tcp_msg.message))) diff --git a/examples/tls_passthrough.py b/examples/tls_passthrough.py index 8c8fa4eb..23afe3ff 100644 --- a/examples/tls_passthrough.py +++ b/examples/tls_passthrough.py @@ -40,6 +40,7 @@ class _TlsStrategy(object): """ Abstract base class for interception strategies. """ + def __init__(self): # A server_address -> interception results mapping self.history = collections.defaultdict(lambda: collections.deque(maxlen=200)) @@ -78,6 +79,7 @@ class ProbabilisticStrategy(_TlsStrategy): """ Fixed probability that we intercept a given connection. """ + def __init__(self, p): self.p = p super(ProbabilisticStrategy, self).__init__() diff --git a/issue_template.md b/issue_template.md index 08d390e4..79389380 100644 --- a/issue_template.md +++ b/issue_template.md @@ -17,3 +17,6 @@ Mitmproxy Version: Operating System: + + +<!-- Please use the mitmproxy forums (https://discourse.mitmproxy.org/) for support/how-to questions. Thanks! :) --> diff --git a/mitmproxy/cmdline.py b/mitmproxy/cmdline.py index 8476f6f3..a873143d 100644 --- a/mitmproxy/cmdline.py +++ b/mitmproxy/cmdline.py @@ -1,14 +1,17 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division + +import base64 import os import re -import base64 import configargparse -from netlib.tcp import Address, sslversion_choices -import netlib.utils -from . import filt, utils, version -from .proxy import config +from mitmproxy import filt +from mitmproxy import version +from mitmproxy.proxy import config +from netlib import human +from netlib import tcp +from netlib.http import url APP_HOST = "mitm.it" APP_PORT = 80 @@ -103,17 +106,17 @@ def parse_setheader(s): return _parse_hook(s) -def parse_server_spec(url): +def parse_server_spec(spec): try: - p = netlib.utils.parse_url(url) + p = url.parse(spec) if p[0] not in ("http", "https"): raise ValueError() except ValueError: raise configargparse.ArgumentTypeError( - "Invalid server specification: %s" % url + "Invalid server specification: %s" % spec ) - address = Address(p[1:3]) + address = tcp.Address(p[1:3]) scheme = p[0].lower() return config.ServerSpec(scheme, address) @@ -135,7 +138,9 @@ def get_common_options(options): if options.stickyauth_filt: stickyauth = options.stickyauth_filt - stream_large_bodies = utils.parse_size(options.stream_large_bodies) + stream_large_bodies = options.stream_large_bodies + if stream_large_bodies: + stream_large_bodies = human.parse_size(stream_large_bodies) reps = [] for i in options.replace: @@ -474,14 +479,14 @@ def proxy_ssl_options(parser): group.add_argument( "--ssl-version-client", dest="ssl_version_client", default="secure", action="store", - choices=sslversion_choices.keys(), + choices=tcp.sslversion_choices.keys(), help="Set supported SSL/TLS versions for client connections. " "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure, which is TLS1.0+." ) group.add_argument( "--ssl-version-server", dest="ssl_version_server", default="secure", action="store", - choices=sslversion_choices.keys(), + choices=tcp.sslversion_choices.keys(), help="Set supported SSL/TLS versions for server connections. " "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure, which is TLS1.0+." ) diff --git a/mitmproxy/console/__init__.py b/mitmproxy/console/__init__.py index 4541a6af..63692ec0 100644 --- a/mitmproxy/console/__init__.py +++ b/mitmproxy/console/__init__.py @@ -1,8 +1,7 @@ -from __future__ import absolute_import, print_function +from __future__ import absolute_import, print_function, division import mailcap import mimetypes -import tempfile import os import os.path import shlex @@ -10,16 +9,28 @@ import signal import stat import subprocess import sys +import tempfile import traceback -import urwid import weakref -from netlib import tcp +import urwid -from .. import flow, script, contentviews -from . import flowlist, flowview, help, window, signals, options -from . import grideditor, palettes, statusbar, palettepicker -from ..exceptions import FlowReadException, ScriptException +from mitmproxy import contentviews +from mitmproxy import controller +from mitmproxy import exceptions +from mitmproxy import flow +from mitmproxy import script +from mitmproxy.console import flowlist +from mitmproxy.console import flowview +from mitmproxy.console import grideditor +from mitmproxy.console import help +from mitmproxy.console import options +from mitmproxy.console import palettepicker +from mitmproxy.console import palettes +from mitmproxy.console import signals +from mitmproxy.console import statusbar +from mitmproxy.console import window +from netlib import tcp EVENTLOG_SIZE = 500 @@ -264,7 +275,7 @@ class ConsoleMaster(flow.FlowMaster): for i in options.scripts: try: self.load_script(i) - except ScriptException as e: + except exceptions.ScriptException as e: print("Script load error: {}".format(e), file=sys.stderr) sys.exit(1) @@ -385,7 +396,7 @@ class ConsoleMaster(flow.FlowMaster): """ try: return flow.read_flows_from_paths(path) - except FlowReadException as e: + except exceptions.FlowReadException as e: signals.status_message.send(message=e.strerror) def client_playback_path(self, path): @@ -669,7 +680,7 @@ class ConsoleMaster(flow.FlowMaster): reterr = None try: flow.FlowMaster.load_flows_file(self, path) - except FlowReadException as e: + except exceptions.FlowReadException as e: reterr = str(e) signals.flowlist_change.send(self) return reterr @@ -699,7 +710,7 @@ class ConsoleMaster(flow.FlowMaster): for command in commands: try: self.load_script(command) - except ScriptException as e: + except exceptions.ScriptException as e: signals.status_message.send( message='Error loading "{}".'.format(command) ) @@ -746,14 +757,15 @@ class ConsoleMaster(flow.FlowMaster): ) def process_flow(self, f): - if self.state.intercept and f.match(self.state.intercept) and not f.request.is_replay: + should_intercept = any( + [ + self.state.intercept and f.match(self.state.intercept) and not f.request.is_replay, + f.intercepted, + ] + ) + if should_intercept: f.intercept(self) - else: - # check if flow was intercepted within an inline script by flow.intercept() - if f.intercepted: - f.intercept(self) - else: - f.reply() + f.reply.take() signals.flowlist_change.send(self) signals.flow_change.send(self, flow = f) @@ -761,26 +773,30 @@ class ConsoleMaster(flow.FlowMaster): self.eventlist[:] = [] # Handlers - def handle_error(self, f): - f = flow.FlowMaster.handle_error(self, f) + @controller.handler + def error(self, f): + f = flow.FlowMaster.error(self, f) if f: self.process_flow(f) return f - def handle_request(self, f): - f = flow.FlowMaster.handle_request(self, f) + @controller.handler + def request(self, f): + f = flow.FlowMaster.request(self, f) if f: self.process_flow(f) return f - def handle_response(self, f): - f = flow.FlowMaster.handle_response(self, f) + @controller.handler + def response(self, f): + f = flow.FlowMaster.response(self, f) if f: self.process_flow(f) return f - def handle_script_change(self, script): - if super(ConsoleMaster, self).handle_script_change(script): + @controller.handler + def script_change(self, script): + if super(ConsoleMaster, self).script_change(script): signals.status_message.send(message='"{}" reloaded.'.format(script.filename)) else: signals.status_message.send(message='Error reloading "{}".'.format(script.filename)) diff --git a/mitmproxy/console/common.py b/mitmproxy/console/common.py index 25658dfa..acb7fc35 100644 --- a/mitmproxy/console/common.py +++ b/mitmproxy/console/common.py @@ -1,16 +1,16 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division -import urwid -import urwid.util import os -import netlib.utils - -from .. import utils -from .. import flow_export -from ..models import decoded -from . import signals +import urwid +import urwid.util +import netlib +from mitmproxy import flow +from mitmproxy import models +from mitmproxy import utils +from mitmproxy.console import signals +from netlib import human try: import pyperclip @@ -259,7 +259,7 @@ def copy_flow_format_data(part, scope, flow): if scope in ("q", "a"): if flow.request.content is None: return None, "Request content is missing" - with decoded(flow.request): + with models.decoded(flow.request): if part == "h": data += netlib.http.http1.assemble_request(flow.request) elif part == "c": @@ -272,7 +272,7 @@ def copy_flow_format_data(part, scope, flow): if scope in ("s", "a") and flow.response: if flow.response.content is None: return None, "Response content is missing" - with decoded(flow.response): + with models.decoded(flow.response): if part == "h": data += netlib.http.http1.assemble_response(flow.response) elif part == "c": @@ -282,16 +282,16 @@ def copy_flow_format_data(part, scope, flow): return data, False -def export_prompt(k, flow): +def export_prompt(k, f): exporters = { - "c": flow_export.curl_command, - "p": flow_export.python_code, - "r": flow_export.raw_request, - "l": flow_export.locust_code, - "t": flow_export.locust_task, + "c": flow.export.curl_command, + "p": flow.export.python_code, + "r": flow.export.raw_request, + "l": flow.export.locust_code, + "t": flow.export.locust_task, } if k in exporters: - copy_to_clipboard_or_prompt(exporters[k](flow)) + copy_to_clipboard_or_prompt(exporters[k](f)) def copy_to_clipboard_or_prompt(data): @@ -419,7 +419,7 @@ def format_flow(f, focus, extended=False, hostheader=False, marked=False): ) if f.response: if f.response.content: - contentdesc = netlib.utils.pretty_size(len(f.response.content)) + contentdesc = human.pretty_size(len(f.response.content)) elif f.response.content is None: contentdesc = "[content missing]" else: @@ -427,7 +427,7 @@ def format_flow(f, focus, extended=False, hostheader=False, marked=False): duration = 0 if f.response.timestamp_end and f.request.timestamp_start: duration = f.response.timestamp_end - f.request.timestamp_start - roundtrip = utils.pretty_duration(duration) + roundtrip = human.pretty_duration(duration) d.update(dict( resp_code = f.response.status_code, diff --git a/mitmproxy/console/flowdetailview.py b/mitmproxy/console/flowdetailview.py index ca083b10..e2c28e71 100644 --- a/mitmproxy/console/flowdetailview.py +++ b/mitmproxy/console/flowdetailview.py @@ -1,7 +1,9 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division + import urwid -from . import common, searchable -from .. import utils + +from mitmproxy import utils +from mitmproxy.console import common, searchable def maybe_timestamp(base, attr): diff --git a/mitmproxy/console/flowlist.py b/mitmproxy/console/flowlist.py index 5672c0d9..8c20c4b6 100644 --- a/mitmproxy/console/flowlist.py +++ b/mitmproxy/console/flowlist.py @@ -1,9 +1,10 @@ -from __future__ import absolute_import -import urwid +from __future__ import absolute_import, print_function, division -import netlib.utils +import urwid -from . import common, signals +import netlib.http.url +from mitmproxy.console import common +from mitmproxy.console import signals def _mkhelp(): @@ -350,7 +351,7 @@ class FlowListBox(urwid.ListBox): ) def new_request(self, url, method): - parts = netlib.utils.parse_url(str(url)) + parts = netlib.http.url.parse(str(url)) if not parts: signals.status_message.send(message="Invalid Url") return diff --git a/mitmproxy/console/flowview.py b/mitmproxy/console/flowview.py index 2010cecd..e9b23176 100644 --- a/mitmproxy/console/flowview.py +++ b/mitmproxy/console/flowview.py @@ -1,17 +1,25 @@ -from __future__ import absolute_import, division +from __future__ import absolute_import, print_function, division + +import math import os -import traceback import sys +import traceback -import math import urwid -from netlib.http import Headers, status_codes -from . import common, grideditor, signals, searchable, tabs -from . import flowdetailview -from .. import utils, controller, contentviews -from ..models import HTTPRequest, HTTPResponse, decoded -from ..exceptions import ContentViewException +from mitmproxy import contentviews +from mitmproxy import controller +from mitmproxy import exceptions +from mitmproxy import models +from mitmproxy import utils +from mitmproxy.console import common +from mitmproxy.console import flowdetailview +from mitmproxy.console import grideditor +from mitmproxy.console import searchable +from mitmproxy.console import signals +from mitmproxy.console import tabs +from netlib.http import Headers +from netlib.http import status_codes class SearchError(Exception): @@ -193,12 +201,12 @@ class FlowView(tabs.Tabs): try: query = None - if isinstance(message, HTTPRequest): + if isinstance(message, models.HTTPRequest): query = message.query description, lines = contentviews.get_content_view( viewmode, message.content, headers=message.headers, query=query ) - except ContentViewException: + except exceptions.ContentViewException: s = "Content viewer failed: \n" + traceback.format_exc() signals.add_event(s, "error") description, lines = contentviews.get_content_view( @@ -207,7 +215,7 @@ class FlowView(tabs.Tabs): description = description.replace("Raw", "Couldn't parse: falling back to Raw") # Give hint that you have to tab for the response. - if description == "No content" and isinstance(message, HTTPRequest): + if description == "No content" and isinstance(message, models.HTTPRequest): description = "No request content (press tab to view response)" # If the users has a wide terminal, he gets fewer lines; this should not be an issue. @@ -372,7 +380,7 @@ class FlowView(tabs.Tabs): message = self.flow.request else: if not self.flow.response: - self.flow.response = HTTPResponse( + self.flow.response = models.HTTPResponse( self.flow.request.http_version, 200, "OK", Headers(), "" ) @@ -399,7 +407,7 @@ class FlowView(tabs.Tabs): ) ) if part == "r": - with decoded(message): + with models.decoded(message): # Fix an issue caused by some editors when editing a # request/response body. Many editors make it hard to save a # file without a terminating newline on the last line. When diff --git a/mitmproxy/console/grideditor.py b/mitmproxy/console/grideditor.py index ea26d966..9fa51ccb 100644 --- a/mitmproxy/console/grideditor.py +++ b/mitmproxy/console/grideditor.py @@ -1,15 +1,18 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import copy -import re import os -import urwid - -from netlib.http import user_agents, cookies +import re -from . import common, signals -from .. import utils, filt, script +import urwid +from mitmproxy import filt +from mitmproxy import script +from mitmproxy import utils +from mitmproxy.console import common +from mitmproxy.console import signals +from netlib.http import cookies +from netlib.http import user_agents FOOTER = [ ('heading_key', "enter"), ":edit ", diff --git a/mitmproxy/console/help.py b/mitmproxy/console/help.py index 0c264ebf..26cb4ed3 100644 --- a/mitmproxy/console/help.py +++ b/mitmproxy/console/help.py @@ -1,9 +1,11 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import urwid -from . import common, signals -from .. import filt, version +from mitmproxy import filt +from mitmproxy import version +from mitmproxy.console import common +from mitmproxy.console import signals footer = [ ("heading", 'mitmproxy v%s ' % version.VERSION), diff --git a/mitmproxy/console/options.py b/mitmproxy/console/options.py index 5c9e0cc9..5a01c9d5 100644 --- a/mitmproxy/console/options.py +++ b/mitmproxy/console/options.py @@ -1,8 +1,13 @@ +from __future__ import absolute_import, print_function, division + import urwid -from .. import contentviews -from . import common, signals, grideditor -from . import select, palettes +from mitmproxy import contentviews +from mitmproxy.console import common +from mitmproxy.console import grideditor +from mitmproxy.console import palettes +from mitmproxy.console import select +from mitmproxy.console import signals footer = [ ('heading_key', "enter/space"), ":toggle ", diff --git a/mitmproxy/console/palettepicker.py b/mitmproxy/console/palettepicker.py index 51ad0606..f2acba0a 100644 --- a/mitmproxy/console/palettepicker.py +++ b/mitmproxy/console/palettepicker.py @@ -1,6 +1,11 @@ +from __future__ import absolute_import, print_function, division + import urwid -from . import select, common, palettes, signals +from mitmproxy.console import common +from mitmproxy.console import palettes +from mitmproxy.console import select +from mitmproxy.console import signals footer = [ ('heading_key', "enter/space"), ":select", diff --git a/mitmproxy/console/palettes.py b/mitmproxy/console/palettes.py index bd370181..36cc3ac0 100644 --- a/mitmproxy/console/palettes.py +++ b/mitmproxy/console/palettes.py @@ -3,6 +3,7 @@ # # http://urwid.org/manual/displayattributes.html # +from __future__ import absolute_import, print_function, division class Palette: diff --git a/mitmproxy/console/pathedit.py b/mitmproxy/console/pathedit.py index 4447070b..0eae9123 100644 --- a/mitmproxy/console/pathedit.py +++ b/mitmproxy/console/pathedit.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import, print_function, division + import glob import os.path diff --git a/mitmproxy/console/searchable.py b/mitmproxy/console/searchable.py index cff1f0a1..c60d1cd9 100644 --- a/mitmproxy/console/searchable.py +++ b/mitmproxy/console/searchable.py @@ -1,6 +1,8 @@ +from __future__ import absolute_import, print_function, division + import urwid -from . import signals +from mitmproxy.console import signals class Highlight(urwid.AttrMap): diff --git a/mitmproxy/console/select.py b/mitmproxy/console/select.py index 928a7ca5..091f07a2 100644 --- a/mitmproxy/console/select.py +++ b/mitmproxy/console/select.py @@ -1,6 +1,8 @@ +from __future__ import absolute_import, print_function, division + import urwid -from . import common +from mitmproxy.console import common class _OptionWidget(urwid.WidgetWrap): @@ -72,7 +74,8 @@ class Heading: return opt -_neg = lambda: False +def _neg(*args): + return False class Option: diff --git a/mitmproxy/console/signals.py b/mitmproxy/console/signals.py index 6a439bf3..b57ebf0c 100644 --- a/mitmproxy/console/signals.py +++ b/mitmproxy/console/signals.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import, print_function, division + import blinker # Show a status message in the action bar diff --git a/mitmproxy/console/statusbar.py b/mitmproxy/console/statusbar.py index 96840a20..af8089b6 100644 --- a/mitmproxy/console/statusbar.py +++ b/mitmproxy/console/statusbar.py @@ -1,9 +1,14 @@ +from __future__ import absolute_import, print_function, division + import os.path import urwid import netlib.utils -from . import pathedit, signals, common +from mitmproxy.console import common +from mitmproxy.console import pathedit +from mitmproxy.console import signals +from netlib import human class ActionBar(urwid.WidgetWrap): @@ -197,7 +202,7 @@ class StatusBar(urwid.WidgetWrap): opts.append("following") if self.master.stream_large_bodies: opts.append( - "stream:%s" % netlib.utils.pretty_size( + "stream:%s" % human.pretty_size( self.master.stream_large_bodies.max_size ) ) @@ -207,7 +212,7 @@ class StatusBar(urwid.WidgetWrap): if self.master.server.config.mode in ["reverse", "upstream"]: dst = self.master.server.config.upstream_server - r.append("[dest:%s]" % netlib.utils.unparse_url( + r.append("[dest:%s]" % netlib.utils.unparse( dst.scheme, dst.address.host, dst.address.port diff --git a/mitmproxy/console/tabs.py b/mitmproxy/console/tabs.py index b5423038..bfcdeba3 100644 --- a/mitmproxy/console/tabs.py +++ b/mitmproxy/console/tabs.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import, print_function, division + import urwid diff --git a/mitmproxy/console/window.py b/mitmproxy/console/window.py index 47c284e4..25780daf 100644 --- a/mitmproxy/console/window.py +++ b/mitmproxy/console/window.py @@ -1,5 +1,8 @@ +from __future__ import absolute_import, print_function, division + import urwid -from . import signals + +from mitmproxy.console import signals class Window(urwid.Frame): diff --git a/mitmproxy/contentviews.py b/mitmproxy/contentviews.py index 1b0f389f..42061a8c 100644 --- a/mitmproxy/contentviews.py +++ b/mitmproxy/contentviews.py @@ -12,26 +12,31 @@ use. For HTTP, the message headers are passed as the ``headers`` keyword argumen requests, the query parameters are passed as the ``query`` keyword argument. """ -from __future__ import (absolute_import, print_function, division) -from six.moves import cStringIO as StringIO +from __future__ import absolute_import, print_function, division + +import datetime import json import logging import subprocess import sys -import lxml.html -import lxml.etree -import datetime -from PIL import Image -from PIL.ExifTags import TAGS + import html2text +import lxml.etree +import lxml.html import six -from netlib.odict import ODict +from PIL import ExifTags +from PIL import Image +from six.moves import cStringIO as StringIO + +import mitmproxy.utils +from mitmproxy import exceptions +from mitmproxy.contrib import jsbeautifier +from mitmproxy.contrib.wbxml import ASCommandResponse from netlib import encoding -from netlib.utils import clean_bin, hexdump, urldecode, multipartdecode, parse_content_type -from . import utils -from .exceptions import ContentViewException -from .contrib import jsbeautifier -from .contrib.wbxml.ASCommandResponse import ASCommandResponse +from netlib import http +from netlib import odict +from netlib.http import url +from netlib import strutils try: import pyamf @@ -120,15 +125,15 @@ class ViewAuto(View): headers = metadata.get("headers", {}) ctype = headers.get("content-type") if data and ctype: - ct = parse_content_type(ctype) if ctype else None + ct = http.parse_content_type(ctype) if ctype else None ct = "%s/%s" % (ct[0], ct[1]) if ct in content_types_map: return content_types_map[ct][0](data, **metadata) - elif utils.isXML(data): + elif strutils.isXML(data): return get("XML")(data, **metadata) if metadata.get("query"): return get("Query")(data, **metadata) - if data and utils.isMostlyBin(data): + if data and strutils.isMostlyBin(data): return get("Hex")(data) if not data: return "No content", [] @@ -151,7 +156,7 @@ class ViewHex(View): @staticmethod def _format(data): - for offset, hexa, s in hexdump(data): + for offset, hexa, s in strutils.hexdump(data): yield [ ("offset", offset + " "), ("text", hexa + " "), @@ -210,7 +215,7 @@ class ViewJSON(View): content_types = ["application/json"] def __call__(self, data, **metadata): - pretty_json = utils.pretty_json(data) + pretty_json = mitmproxy.utils.pretty_json(data) if pretty_json: return "JSON", format_text(pretty_json) @@ -221,7 +226,7 @@ class ViewHTML(View): content_types = ["text/html"] def __call__(self, data, **metadata): - if utils.isXML(data): + if strutils.isXML(data): parser = lxml.etree.HTMLParser( strip_cdata=True, remove_blank_text=True @@ -257,8 +262,8 @@ class ViewURLEncoded(View): content_types = ["application/x-www-form-urlencoded"] def __call__(self, data, **metadata): - d = urldecode(data) - return "URLEncoded form", format_dict(ODict(d)) + d = url.decode(data) + return "URLEncoded form", format_dict(odict.ODict(d)) class ViewMultipart(View): @@ -269,12 +274,12 @@ class ViewMultipart(View): @staticmethod def _format(v): yield [("highlight", "Form data:\n")] - for message in format_dict(ODict(v)): + for message in format_dict(odict.ODict(v)): yield message def __call__(self, data, **metadata): headers = metadata.get("headers", {}) - v = multipartdecode(headers, data) + v = http.multipart.decode(headers, data) if v: return "Multipart form", self._format(v) @@ -414,11 +419,11 @@ class ViewImage(View): ex = img._getexif() if ex: for i in sorted(ex.keys()): - tag = TAGS.get(i, i) + tag = ExifTags.TAGS.get(i, i) parts.append( (str(tag), str(ex[i])) ) - fmt = format_dict(ODict(parts)) + fmt = format_dict(odict.ODict(parts)) return "%s image" % img.format, fmt @@ -489,7 +494,7 @@ class ViewWBXML(View): def __call__(self, data, **metadata): try: - parser = ASCommandResponse(data) + parser = ASCommandResponse.ASCommandResponse(data) parsedContent = parser.xmlString if parsedContent: return "WBXML", format_text(parsedContent) @@ -518,12 +523,12 @@ def add(view): # TODO: auto-select a different name (append an integer?) for i in views: if i.name == view.name: - raise ContentViewException("Duplicate view: " + view.name) + raise exceptions.ContentViewException("Duplicate view: " + view.name) # TODO: the UI should auto-prompt for a replacement shortcut for prompt in view_prompts: if prompt[1] == view.prompt[1]: - raise ContentViewException("Duplicate view shortcut: " + view.prompt[1]) + raise exceptions.ContentViewException("Duplicate view shortcut: " + view.prompt[1]) views.append(view) @@ -576,9 +581,9 @@ def safe_to_print(lines, encoding="utf8"): clean_line = [] for (style, text) in line: try: - text = clean_bin(text.decode(encoding, "strict")) + text = strutils.clean_bin(text.decode(encoding, "strict")) except UnicodeDecodeError: - text = clean_bin(text).decode(encoding, "strict") + text = strutils.clean_bin(text).decode(encoding, "strict") clean_line.append((style, text)) yield clean_line @@ -610,8 +615,8 @@ def get_content_view(viewmode, data, **metadata): # Third-party viewers can fail in unexpected ways... except Exception as e: six.reraise( - ContentViewException, - ContentViewException(str(e)), + exceptions.ContentViewException, + exceptions.ContentViewException(str(e)), sys.exc_info()[2] ) if not ret: diff --git a/mitmproxy/controller.py b/mitmproxy/controller.py index 81978a09..1498c3ad 100644 --- a/mitmproxy/controller.py +++ b/mitmproxy/controller.py @@ -1,21 +1,57 @@ -from __future__ import absolute_import -from six.moves import queue +from __future__ import absolute_import, print_function, division + +import functools import threading -from .exceptions import Kill +from six.moves import queue + +from mitmproxy import exceptions + +Events = frozenset([ + "clientconnect", + "clientdisconnect", + "serverconnect", + "serverdisconnect", + + "tcp_open", + "tcp_message", + "tcp_error", + "tcp_close", + + "request", + "response", + "responseheaders", + + "next_layer", + + "error", + "log", + + "script_change", +]) class Master(object): """ - The master handles mitmproxy's main event loop. + The master handles mitmproxy's main event loop. """ - - def __init__(self): + def __init__(self, *servers): self.event_queue = queue.Queue() self.should_exit = threading.Event() + self.servers = [] + for i in servers: + self.add_server(i) + + def add_server(self, server): + # We give a Channel to the server which can be used to communicate with the master + channel = Channel(self.event_queue, self.should_exit) + server.set_channel(channel) + self.servers.append(server) def start(self): self.should_exit.clear() + for server in self.servers: + ServerThread(server).start() def run(self): self.start() @@ -35,7 +71,17 @@ class Master(object): # exception is thrown. while True: mtype, obj = self.event_queue.get(timeout=timeout) - handle_func = getattr(self, "handle_" + mtype) + if mtype not in Events: + raise exceptions.ControlException("Unknown event %s" % repr(mtype)) + handle_func = getattr(self, mtype) + if not hasattr(handle_func, "func_dict"): + raise exceptions.ControlException("Handler %s not a function" % mtype) + if not handle_func.func_dict.get("__handler"): + raise exceptions.ControlException( + "Handler function %s is not decorated with controller.handler" % ( + handle_func + ) + ) handle_func(obj) self.event_queue.task_done() changed = True @@ -44,33 +90,9 @@ class Master(object): return changed def shutdown(self): - self.should_exit.set() - - -class ServerMaster(Master): - """ - The ServerMaster adds server thread support to the master. - """ - - def __init__(self): - super(ServerMaster, self).__init__() - self.servers = [] - - def add_server(self, server): - # We give a Channel to the server which can be used to communicate with the master - channel = Channel(self.event_queue, self.should_exit) - server.set_channel(channel) - self.servers.append(server) - - def start(self): - super(ServerMaster, self).start() - for server in self.servers: - ServerThread(server).start() - - def shutdown(self): for server in self.servers: server.shutdown() - super(ServerMaster, self).shutdown() + self.should_exit.set() class ServerThread(threading.Thread): @@ -86,10 +108,9 @@ class ServerThread(threading.Thread): class Channel(object): """ - The only way for the proxy server to communicate with the master - is to use the channel it has been given. + The only way for the proxy server to communicate with the master + is to use the channel it has been given. """ - def __init__(self, q, should_exit): self.q = q self.should_exit = should_exit @@ -100,7 +121,7 @@ class Channel(object): master. Then wait for a response. Raises: - Kill: All connections should be closed immediately. + exceptions.Kill: All connections should be closed immediately. """ m.reply = Reply(m) self.q.put((mtype, m)) @@ -110,11 +131,10 @@ class Channel(object): g = m.reply.q.get(timeout=0.5) except queue.Empty: # pragma: no cover continue - if g == Kill: - raise Kill() + if g == exceptions.Kill: + raise exceptions.Kill() return g - - raise Kill() + raise exceptions.Kill() def tell(self, mtype, m): """ @@ -130,9 +150,13 @@ class DummyReply(object): A reply object that does nothing. Useful when we need an object to seem like it has a channel, and during testing. """ - def __init__(self): self.acked = False + self.taken = False + self.handled = False + + def take(self): + self.taken = True def __call__(self, msg=False): self.acked = True @@ -142,22 +166,68 @@ class DummyReply(object): NO_REPLY = object() +def handler(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + # We can either be called as a method, or as a wrapped solo function + if len(args) == 1: + message = args[0] + elif len(args) == 2: + message = args[1] + else: + raise exceptions.ControlException("Handler takes one argument: a message") + + if not hasattr(message, "reply"): + raise exceptions.ControlException("Message %s has no reply attribute" % message) + + # The following ensures that inheritance with wrapped handlers in the + # base class works. If we're the first handler, then responsibility for + # acking is ours. If not, it's someone else's and we ignore it. + handling = False + # We're the first handler - ack responsibility is ours + if not message.reply.handled: + handling = True + message.reply.handled = True + + ret = f(*args, **kwargs) + + if handling and not message.reply.acked and not message.reply.taken: + message.reply() + return ret + # Mark this function as a handler wrapper + wrapper.func_dict["__handler"] = True + return wrapper + + class Reply(object): """ Messages sent through a channel are decorated with a "reply" attribute. This object is used to respond to the message through the return channel. """ - def __init__(self, obj): self.obj = obj self.q = queue.Queue() + # Has this message been acked? self.acked = False + # Has the user taken responsibility for ack-ing? + self.taken = False + # Has a handler taken responsibility for ack-ing? + self.handled = False + + def take(self): + self.taken = True def __call__(self, msg=NO_REPLY): + if self.acked: + raise exceptions.ControlException("Message already acked.") + self.acked = True + if msg is NO_REPLY: + self.q.put(self.obj) + else: + self.q.put(msg) + + def __del__(self): if not self.acked: - self.acked = True - if msg is NO_REPLY: - self.q.put(self.obj) - else: - self.q.put(msg) + # This will be ignored by the interpreter, but emit a warning + raise exceptions.ControlException("Un-acked message") diff --git a/mitmproxy/dump.py b/mitmproxy/dump.py index f1eabdb8..cc6896ed 100644 --- a/mitmproxy/dump.py +++ b/mitmproxy/dump.py @@ -1,13 +1,19 @@ -from __future__ import absolute_import, print_function -import traceback +from __future__ import absolute_import, print_function, division + +import itertools import sys +import traceback + import click -import itertools +from mitmproxy import contentviews +from mitmproxy import controller +from mitmproxy import exceptions +from mitmproxy import filt +from mitmproxy import flow +from netlib import human from netlib import tcp -from netlib.utils import bytes_to_escaped_str, pretty_size -from . import flow, filt, contentviews -from .exceptions import ContentViewException, FlowReadException, ScriptException +from netlib import strutils class DumpError(Exception): @@ -74,8 +80,8 @@ class DumpMaster(flow.FlowMaster): if self.server and self.server.config.http2 and not tcp.HAS_ALPN: # pragma: no cover print("ALPN support missing (OpenSSL 1.0.2+ required)!\n" - "HTTP/2 is disabled. Use --no-http2 to silence this warning.", - file=sys.stderr) + "HTTP/2 is disabled. Use --no-http2 to silence this warning.", + file=sys.stderr) if options.filtstr: self.filt = filt.parse(options.filtstr) @@ -127,13 +133,13 @@ class DumpMaster(flow.FlowMaster): for command in scripts: try: self.load_script(command, use_reloader=True) - except ScriptException as e: + except exceptions.ScriptException as e: raise DumpError(str(e)) if options.rfile: try: self.load_flows_file(options.rfile) - except FlowReadException as v: + except exceptions.FlowReadException as v: self.add_event("Flow file corrupted.", "error") raise DumpError(v) @@ -147,7 +153,7 @@ class DumpMaster(flow.FlowMaster): """ try: return flow.read_flows_from_paths(paths) - except FlowReadException as e: + except exceptions.FlowReadException as e: raise DumpError(str(e)) def add_event(self, e, level="info"): @@ -175,8 +181,8 @@ class DumpMaster(flow.FlowMaster): if self.o.flow_detail >= 2: headers = "\r\n".join( "{}: {}".format( - click.style(bytes_to_escaped_str(k), fg="blue", bold=True), - click.style(bytes_to_escaped_str(v), fg="blue")) + click.style(strutils.bytes_to_escaped_str(k), fg="blue", bold=True), + click.style(strutils.bytes_to_escaped_str(v), fg="blue")) for k, v in message.headers.fields ) self.echo(headers, indent=4) @@ -192,7 +198,7 @@ class DumpMaster(flow.FlowMaster): message.content, headers=message.headers ) - except ContentViewException: + except exceptions.ContentViewException: s = "Content viewer failed: \n" + traceback.format_exc() self.add_event(s, "debug") type, lines = contentviews.get_content_view( @@ -238,7 +244,7 @@ class DumpMaster(flow.FlowMaster): stickycookie = "" if flow.client_conn: - client = click.style(bytes_to_escaped_str(flow.client_conn.address.host), bold=True) + client = click.style(strutils.bytes_to_escaped_str(flow.client_conn.address.host), bold=True) else: client = click.style("[replay]", fg="yellow", bold=True) @@ -247,12 +253,12 @@ class DumpMaster(flow.FlowMaster): GET="green", DELETE="red" ).get(method.upper(), "magenta") - method = click.style(bytes_to_escaped_str(method), fg=method_color, bold=True) + method = click.style(strutils.bytes_to_escaped_str(method), fg=method_color, bold=True) if self.showhost: url = flow.request.pretty_url else: url = flow.request.url - url = click.style(bytes_to_escaped_str(url), bold=True) + url = click.style(strutils.bytes_to_escaped_str(url), bold=True) httpversion = "" if flow.request.http_version not in ("HTTP/1.1", "HTTP/1.0"): @@ -282,12 +288,12 @@ class DumpMaster(flow.FlowMaster): elif 400 <= code < 600: code_color = "red" code = click.style(str(code), fg=code_color, bold=True, blink=(code == 418)) - reason = click.style(bytes_to_escaped_str(flow.response.reason), fg=code_color, bold=True) + reason = click.style(strutils.bytes_to_escaped_str(flow.response.reason), fg=code_color, bold=True) if flow.response.content is None: size = "(content missing)" else: - size = pretty_size(len(flow.response.content)) + size = human.pretty_size(len(flow.response.content)) size = click.style(size, bold=True) arrows = click.style("<<", bold=True) @@ -325,22 +331,23 @@ class DumpMaster(flow.FlowMaster): self.echo_flow(f) - def handle_request(self, f): - flow.FlowMaster.handle_request(self, f) - self.state.delete_flow(f) + @controller.handler + def request(self, f): + f = flow.FlowMaster.request(self, f) if f: - f.reply() + self.state.delete_flow(f) return f - def handle_response(self, f): - flow.FlowMaster.handle_response(self, f) + @controller.handler + def response(self, f): + f = flow.FlowMaster.response(self, f) if f: - f.reply() self._process_flow(f) return f - def handle_error(self, f): - flow.FlowMaster.handle_error(self, f) + @controller.handler + def error(self, f): + flow.FlowMaster.error(self, f) if f: self._process_flow(f) return f diff --git a/mitmproxy/exceptions.py b/mitmproxy/exceptions.py index 8f989063..d97b9498 100644 --- a/mitmproxy/exceptions.py +++ b/mitmproxy/exceptions.py @@ -5,14 +5,14 @@ Every Exception mitmproxy raises shall be a subclass of ProxyException. See also: http://lucumr.pocoo.org/2014/10/16/on-error-handling/ """ -from __future__ import (absolute_import, print_function, division) - -import traceback +from __future__ import absolute_import, print_function, division import sys +import traceback class ProxyException(Exception): + """ Base class for all exceptions thrown by mitmproxy. """ @@ -22,6 +22,7 @@ class ProxyException(Exception): class Kill(ProxyException): + """ Signal that both client and server connection(s) should be killed immediately. """ @@ -37,6 +38,7 @@ class TlsProtocolException(ProtocolException): class ClientHandshakeException(TlsProtocolException): + def __init__(self, message, server): super(ClientHandshakeException, self).__init__(message) self.server = server @@ -67,6 +69,7 @@ class ReplayException(ProxyException): class ScriptException(ProxyException): + @classmethod def from_exception_context(cls, cut_tb=1): """ @@ -88,3 +91,7 @@ class ScriptException(ProxyException): class FlowReadException(ProxyException): pass + + +class ControlException(Exception): + pass diff --git a/mitmproxy/filt.py b/mitmproxy/filt.py index f34969dd..d98e3749 100644 --- a/mitmproxy/filt.py +++ b/mitmproxy/filt.py @@ -31,13 +31,16 @@ ~c CODE Response code. rex Equivalent to ~u rex """ -from __future__ import absolute_import, print_function +from __future__ import absolute_import, print_function, division + import re import sys + import pyparsing as pp class _Token(object): + def dump(self, indent=0, fp=sys.stdout): print("{spacing}{name}{expr}".format( spacing="\t" * indent, diff --git a/mitmproxy/flow.py b/mitmproxy/flow.py deleted file mode 100644 index a09a81a7..00000000 --- a/mitmproxy/flow.py +++ /dev/null @@ -1,1242 +0,0 @@ -""" - This module provides more sophisticated flow tracking and provides filtering and interception facilities. -""" -from __future__ import absolute_import - -from abc import abstractmethod, ABCMeta -import hashlib -import sys - -import six -from six.moves import http_cookies, http_cookiejar, urllib -import os -import re - -from typing import List, Optional, Set - -from netlib import wsgi, odict -from netlib.exceptions import HttpException -from netlib.http import Headers, http1, cookies -from netlib.utils import clean_bin -from . import controller, tnetstring, filt, script, version, flow_format_compat -from .onboarding import app -from .proxy.config import HostMatcher -from .protocol.http_replay import RequestReplayThread -from .exceptions import Kill, FlowReadException -from .models import ClientConnection, ServerConnection, HTTPFlow, HTTPRequest, FLOW_TYPES, TCPFlow -from collections import defaultdict - - -class AppRegistry: - - def __init__(self): - self.apps = {} - - def add(self, app, domain, port): - """ - Add a WSGI app to the registry, to be served for requests to the - specified domain, on the specified port. - """ - self.apps[(domain, port)] = wsgi.WSGIAdaptor( - app, - domain, - port, - version.NAMEVERSION - ) - - def get(self, request): - """ - Returns an WSGIAdaptor instance if request matches an app, or None. - """ - if (request.host, request.port) in self.apps: - return self.apps[(request.host, request.port)] - if "host" in request.headers: - host = request.headers["host"] - return self.apps.get((host, request.port), None) - - -class ReplaceHooks: - - def __init__(self): - self.lst = [] - - def set(self, r): - self.clear() - for i in r: - self.add(*i) - - def add(self, fpatt, rex, s): - """ - add a replacement hook. - - fpatt: a string specifying a filter pattern. - rex: a regular expression. - s: the replacement string - - returns true if hook was added, false if the pattern could not be - parsed. - """ - cpatt = filt.parse(fpatt) - if not cpatt: - return False - try: - re.compile(rex) - except re.error: - return False - self.lst.append((fpatt, rex, s, cpatt)) - return True - - def get_specs(self): - """ - Retrieve the hook specifcations. Returns a list of (fpatt, rex, s) - tuples. - """ - return [i[:3] for i in self.lst] - - def count(self): - return len(self.lst) - - def run(self, f): - for _, rex, s, cpatt in self.lst: - if cpatt(f): - if f.response: - f.response.replace(rex, s) - else: - f.request.replace(rex, s) - - def clear(self): - self.lst = [] - - -class SetHeaders: - - def __init__(self): - self.lst = [] - - def set(self, r): - self.clear() - for i in r: - self.add(*i) - - def add(self, fpatt, header, value): - """ - Add a set header hook. - - fpatt: String specifying a filter pattern. - header: Header name. - value: Header value string - - Returns True if hook was added, False if the pattern could not be - parsed. - """ - cpatt = filt.parse(fpatt) - if not cpatt: - return False - self.lst.append((fpatt, header, value, cpatt)) - return True - - def get_specs(self): - """ - Retrieve the hook specifcations. Returns a list of (fpatt, rex, s) - tuples. - """ - return [i[:3] for i in self.lst] - - def count(self): - return len(self.lst) - - def clear(self): - self.lst = [] - - def run(self, f): - for _, header, value, cpatt in self.lst: - if cpatt(f): - if f.response: - f.response.headers.pop(header, None) - else: - f.request.headers.pop(header, None) - for _, header, value, cpatt in self.lst: - if cpatt(f): - if f.response: - f.response.headers.add(header, value) - else: - f.request.headers.add(header, value) - - -class StreamLargeBodies(object): - - def __init__(self, max_size): - self.max_size = max_size - - def run(self, flow, is_request): - r = flow.request if is_request else flow.response - expected_size = http1.expected_http_body_size( - flow.request, flow.response if not is_request else None - ) - if not r.content and not (0 <= expected_size <= self.max_size): - # r.stream may already be a callable, which we want to preserve. - r.stream = r.stream or True - - -class ClientPlaybackState: - - def __init__(self, flows, exit): - self.flows, self.exit = flows, exit - self.current = None - self.testing = False # Disables actual replay for testing. - - def count(self): - return len(self.flows) - - def done(self): - if len(self.flows) == 0 and not self.current: - return True - return False - - def clear(self, flow): - """ - A request has returned in some way - if this is the one we're - servicing, go to the next flow. - """ - if flow is self.current: - self.current = None - - def tick(self, master): - if self.flows and not self.current: - self.current = self.flows.pop(0).copy() - if not self.testing: - master.replay_request(self.current) - else: - self.current.reply = controller.DummyReply() - master.handle_request(self.current) - if self.current.response: - master.handle_response(self.current) - - -class ServerPlaybackState: - - def __init__( - self, - headers, - flows, - exit, - nopop, - ignore_params, - ignore_content, - ignore_payload_params, - ignore_host): - """ - headers: Case-insensitive list of request headers that should be - included in request-response matching. - """ - self.headers = headers - self.exit = exit - self.nopop = nopop - self.ignore_params = ignore_params - self.ignore_content = ignore_content - self.ignore_payload_params = ignore_payload_params - self.ignore_host = ignore_host - self.fmap = {} - for i in flows: - if i.response: - l = self.fmap.setdefault(self._hash(i), []) - l.append(i) - - def count(self): - return sum(len(i) for i in self.fmap.values()) - - def _hash(self, flow): - """ - Calculates a loose hash of the flow request. - """ - r = flow.request - - _, _, path, _, query, _ = urllib.parse.urlparse(r.url) - queriesArray = urllib.parse.parse_qsl(query, keep_blank_values=True) - - key = [ - str(r.port), - str(r.scheme), - str(r.method), - str(path), - ] - - if not self.ignore_content: - form_contents = r.urlencoded_form or r.multipart_form - if self.ignore_payload_params and form_contents: - key.extend( - p for p in form_contents.items(multi=True) - if p[0] not in self.ignore_payload_params - ) - else: - key.append(str(r.content)) - - if not self.ignore_host: - key.append(r.host) - - filtered = [] - ignore_params = self.ignore_params or [] - for p in queriesArray: - if p[0] not in ignore_params: - filtered.append(p) - for p in filtered: - key.append(p[0]) - key.append(p[1]) - - if self.headers: - headers = [] - for i in self.headers: - v = r.headers.get(i) - headers.append((i, v)) - key.append(headers) - return hashlib.sha256(repr(key)).digest() - - def next_flow(self, request): - """ - Returns the next flow object, or None if no matching flow was - found. - """ - l = self.fmap.get(self._hash(request)) - if not l: - return None - - if self.nopop: - return l[0] - else: - return l.pop(0) - - -class StickyCookieState: - - def __init__(self, flt): - """ - flt: Compiled filter. - """ - self.jar = defaultdict(dict) - self.flt = flt - - def ckey(self, attrs, f): - """ - Returns a (domain, port, path) tuple. - """ - domain = f.request.host - path = "/" - if "domain" in attrs: - domain = attrs["domain"] - if "path" in attrs: - path = attrs["path"] - return (domain, f.request.port, path) - - def domain_match(self, a, b): - if http_cookiejar.domain_match(a, b): - return True - elif http_cookiejar.domain_match(a, b.strip(".")): - return True - return False - - def handle_response(self, f): - for name, (value, attrs) in f.response.cookies.items(multi=True): - # FIXME: We now know that Cookie.py screws up some cookies with - # valid RFC 822/1123 datetime specifications for expiry. Sigh. - a = self.ckey(attrs, f) - if self.domain_match(f.request.host, a[0]): - b = attrs.with_insert(0, name, value) - self.jar[a][name] = b - - def handle_request(self, f): - l = [] - if f.match(self.flt): - for domain, port, path in self.jar.keys(): - match = [ - self.domain_match(f.request.host, domain), - f.request.port == port, - f.request.path.startswith(path) - ] - if all(match): - c = self.jar[(domain, port, path)] - l.extend([cookies.format_cookie_header(c[name].items(multi=True)) for name in c.keys()]) - if l: - f.request.stickycookie = True - f.request.headers["cookie"] = "; ".join(l) - - -class StickyAuthState: - - def __init__(self, flt): - """ - flt: Compiled filter. - """ - self.flt = flt - self.hosts = {} - - def handle_request(self, f): - host = f.request.host - if "authorization" in f.request.headers: - self.hosts[host] = f.request.headers["authorization"] - elif f.match(self.flt): - if host in self.hosts: - f.request.headers["authorization"] = self.hosts[host] - - -@six.add_metaclass(ABCMeta) -class FlowList(object): - - def __init__(self): - self._list = [] # type: List[Flow] - - def __iter__(self): - return iter(self._list) - - def __contains__(self, item): - return item in self._list - - def __getitem__(self, item): - return self._list[item] - - def __bool__(self): - return bool(self._list) - - if six.PY2: - __nonzero__ = __bool__ - - def __len__(self): - return len(self._list) - - def index(self, f): - return self._list.index(f) - - @abstractmethod - def _add(self, f): - return - - @abstractmethod - def _update(self, f): - return - - @abstractmethod - def _remove(self, f): - return - - -class FlowView(FlowList): - - def __init__(self, store, filt=None): - super(FlowView, self).__init__() - if not filt: - filt = lambda flow: True - self._build(store, filt) - - self.store = store - self.store.views.append(self) - - def _close(self): - self.store.views.remove(self) - - def _build(self, flows, filt=None): - if filt: - self.filt = filt - self._list = list(filter(self.filt, flows)) - - def _add(self, f): - if self.filt(f): - self._list.append(f) - - def _update(self, f): - if f not in self._list: - self._add(f) - elif not self.filt(f): - self._remove(f) - - def _remove(self, f): - if f in self._list: - self._list.remove(f) - - def _recalculate(self, flows): - self._build(flows) - - -class FlowStore(FlowList): - - """ - Responsible for handling flows in the state: - Keeps a list of all flows and provides views on them. - """ - - def __init__(self): - super(FlowStore, self).__init__() - self._set = set() # Used for O(1) lookups - self.views = [] - self._recalculate_views() - - def get(self, flow_id): - for f in self._list: - if f.id == flow_id: - return f - - def __contains__(self, f): - return f in self._set - - def _add(self, f): - """ - Adds a flow to the state. - The flow to add must not be present in the state. - """ - self._list.append(f) - self._set.add(f) - for view in self.views: - view._add(f) - - def _update(self, f): - """ - Notifies the state that a flow has been updated. - The flow must be present in the state. - """ - if f in self: - for view in self.views: - view._update(f) - - def _remove(self, f): - """ - Deletes a flow from the state. - The flow must be present in the state. - """ - self._list.remove(f) - self._set.remove(f) - for view in self.views: - view._remove(f) - - # Expensive bulk operations - - def _extend(self, flows): - """ - Adds a list of flows to the state. - The list of flows to add must not contain flows that are already in the state. - """ - self._list.extend(flows) - self._set.update(flows) - self._recalculate_views() - - def _clear(self): - self._list = [] - self._set = set() - self._recalculate_views() - - def _recalculate_views(self): - """ - Expensive operation: Recalculate all the views after a bulk change. - """ - for view in self.views: - view._recalculate(self) - - # Utility functions. - # There are some common cases where we need to argue about all flows - # irrespective of filters on the view etc (i.e. on shutdown). - - def active_count(self): - c = 0 - for i in self._list: - if not i.response and not i.error: - c += 1 - return c - - # TODO: Should accept_all operate on views or on all flows? - def accept_all(self, master): - for f in self._list: - f.accept_intercept(master) - - def kill_all(self, master): - for f in self._list: - f.kill(master) - - -class State(object): - - def __init__(self): - self.flows = FlowStore() - self.view = FlowView(self.flows, None) - - # These are compiled filt expressions: - self.intercept = None - - @property - def limit_txt(self): - return getattr(self.view.filt, "pattern", None) - - def flow_count(self): - return len(self.flows) - - # TODO: All functions regarding flows that don't cause side-effects should - # be moved into FlowStore. - def index(self, f): - return self.flows.index(f) - - def active_flow_count(self): - return self.flows.active_count() - - def add_flow(self, f): - """ - Add a request to the state. - """ - self.flows._add(f) - return f - - def update_flow(self, f): - """ - Add a response to the state. - """ - self.flows._update(f) - return f - - def delete_flow(self, f): - self.flows._remove(f) - - def load_flows(self, flows): - self.flows._extend(flows) - - def set_limit(self, txt): - if txt == self.limit_txt: - return - if txt: - f = filt.parse(txt) - if not f: - return "Invalid filter expression." - self.view._close() - self.view = FlowView(self.flows, f) - else: - self.view._close() - self.view = FlowView(self.flows, None) - - def set_intercept(self, txt): - if txt: - f = filt.parse(txt) - if not f: - return "Invalid filter expression." - self.intercept = f - else: - self.intercept = None - - @property - def intercept_txt(self): - return getattr(self.intercept, "pattern", None) - - def clear(self): - self.flows._clear() - - def accept_all(self, master): - self.flows.accept_all(master) - - def backup(self, f): - f.backup() - self.update_flow(f) - - def revert(self, f): - f.revert() - self.update_flow(f) - - def killall(self, master): - self.flows.kill_all(master) - - -class FlowMaster(controller.ServerMaster): - - @property - def server(self): - # At some point, we may want to have support for multiple servers. - # For now, this suffices. - if len(self.servers) > 0: - return self.servers[0] - - def __init__(self, server, state): - super(FlowMaster, self).__init__() - if server: - self.add_server(server) - self.state = state - self.active_flows = set() # type: Set[Flow] - self.server_playback = None # type: Optional[ServerPlaybackState] - self.client_playback = None # type: Optional[ClientPlaybackState] - self.kill_nonreplay = False - self.scripts = [] # type: List[script.Script] - self.pause_scripts = False - - self.stickycookie_state = None # type: Optional[StickyCookieState] - self.stickycookie_txt = None - - self.stickyauth_state = False # type: Optional[StickyAuthState] - self.stickyauth_txt = None - - self.anticache = False - self.anticomp = False - self.stream_large_bodies = None # type: Optional[StreamLargeBodies] - self.refresh_server_playback = False - self.replacehooks = ReplaceHooks() - self.setheaders = SetHeaders() - self.replay_ignore_params = False - self.replay_ignore_content = None - self.replay_ignore_host = False - - self.stream = None - self.apps = AppRegistry() - - def start_app(self, host, port): - self.apps.add( - app.mapp, - host, - port - ) - - def add_event(self, e, level="info"): - """ - level: debug, info, error - """ - - def unload_scripts(self): - for s in self.scripts[:]: - self.unload_script(s) - - def unload_script(self, script_obj): - try: - script_obj.unload() - except script.ScriptException as e: - self.add_event("Script error:\n" + str(e), "error") - script.reloader.unwatch(script_obj) - self.scripts.remove(script_obj) - - def load_script(self, command, use_reloader=False): - """ - Loads a script. - - Raises: - ScriptException - """ - s = script.Script(command, script.ScriptContext(self)) - s.load() - if use_reloader: - script.reloader.watch(s, lambda: self.event_queue.put(("script_change", s))) - self.scripts.append(s) - - def _run_single_script_hook(self, script_obj, name, *args, **kwargs): - if script_obj and not self.pause_scripts: - try: - script_obj.run(name, *args, **kwargs) - except script.ScriptException as e: - self.add_event("Script error:\n{}".format(e), "error") - - def run_script_hook(self, name, *args, **kwargs): - for script_obj in self.scripts: - self._run_single_script_hook(script_obj, name, *args, **kwargs) - - def get_ignore_filter(self): - return self.server.config.check_ignore.patterns - - def set_ignore_filter(self, host_patterns): - self.server.config.check_ignore = HostMatcher(host_patterns) - - def get_tcp_filter(self): - return self.server.config.check_tcp.patterns - - def set_tcp_filter(self, host_patterns): - self.server.config.check_tcp = HostMatcher(host_patterns) - - def set_stickycookie(self, txt): - if txt: - flt = filt.parse(txt) - if not flt: - return "Invalid filter expression." - self.stickycookie_state = StickyCookieState(flt) - self.stickycookie_txt = txt - else: - self.stickycookie_state = None - self.stickycookie_txt = None - - def set_stream_large_bodies(self, max_size): - if max_size is not None: - self.stream_large_bodies = StreamLargeBodies(max_size) - else: - self.stream_large_bodies = False - - def set_stickyauth(self, txt): - if txt: - flt = filt.parse(txt) - if not flt: - return "Invalid filter expression." - self.stickyauth_state = StickyAuthState(flt) - self.stickyauth_txt = txt - else: - self.stickyauth_state = None - self.stickyauth_txt = None - - def start_client_playback(self, flows, exit): - """ - flows: List of flows. - """ - self.client_playback = ClientPlaybackState(flows, exit) - - def stop_client_playback(self): - self.client_playback = None - - def start_server_playback( - self, - flows, - kill, - headers, - exit, - nopop, - ignore_params, - ignore_content, - ignore_payload_params, - ignore_host): - """ - flows: List of flows. - kill: Boolean, should we kill requests not part of the replay? - ignore_params: list of parameters to ignore in server replay - ignore_content: true if request content should be ignored in server replay - ignore_payload_params: list of content params to ignore in server replay - ignore_host: true if request host should be ignored in server replay - """ - self.server_playback = ServerPlaybackState( - headers, - flows, - exit, - nopop, - ignore_params, - ignore_content, - ignore_payload_params, - ignore_host) - self.kill_nonreplay = kill - - def stop_server_playback(self): - self.server_playback = None - - def do_server_playback(self, flow): - """ - This method should be called by child classes in the handle_request - handler. Returns True if playback has taken place, None if not. - """ - if self.server_playback: - rflow = self.server_playback.next_flow(flow) - if not rflow: - return None - response = rflow.response.copy() - response.is_replay = True - if self.refresh_server_playback: - response.refresh() - flow.response = response - return True - return None - - def tick(self, timeout): - if self.client_playback: - stop = ( - self.client_playback.done() and - self.state.active_flow_count() == 0 - ) - exit = self.client_playback.exit - if stop: - self.stop_client_playback() - if exit: - self.shutdown() - else: - self.client_playback.tick(self) - - if self.server_playback: - stop = ( - self.server_playback.count() == 0 and - self.state.active_flow_count() == 0 and - not self.kill_nonreplay - ) - exit = self.server_playback.exit - if stop: - self.stop_server_playback() - if exit: - self.shutdown() - return super(FlowMaster, self).tick(timeout) - - def duplicate_flow(self, f): - f2 = f.copy() - self.load_flow(f2) - return f2 - - def create_request(self, method, scheme, host, port, path): - """ - this method creates a new artificial and minimalist request also adds it to flowlist - """ - c = ClientConnection.make_dummy(("", 0)) - s = ServerConnection.make_dummy((host, port)) - - f = HTTPFlow(c, s) - headers = Headers() - - req = HTTPRequest( - "absolute", - method, - scheme, - host, - port, - path, - b"HTTP/1.1", - headers, - b"" - ) - f.request = req - self.load_flow(f) - return f - - def load_flow(self, f): - """ - Loads a flow - """ - if isinstance(f, HTTPFlow): - if self.server and self.server.config.mode == "reverse": - f.request.host = self.server.config.upstream_server.address.host - f.request.port = self.server.config.upstream_server.address.port - f.request.scheme = self.server.config.upstream_server.scheme - - f.reply = controller.DummyReply() - if f.request: - self.handle_request(f) - if f.response: - self.handle_responseheaders(f) - self.handle_response(f) - if f.error: - self.handle_error(f) - elif isinstance(f, TCPFlow): - messages = f.messages - f.messages = [] - f.reply = controller.DummyReply() - self.handle_tcp_open(f) - while messages: - f.messages.append(messages.pop(0)) - self.handle_tcp_message(f) - if f.error: - self.handle_tcp_error(f) - self.handle_tcp_close(f) - else: - raise NotImplementedError() - - def load_flows(self, fr): - """ - Load flows from a FlowReader object. - """ - cnt = 0 - for i in fr.stream(): - cnt += 1 - self.load_flow(i) - return cnt - - def load_flows_file(self, path): - path = os.path.expanduser(path) - try: - if path == "-": - # This is incompatible with Python 3 - maybe we can use click? - freader = FlowReader(sys.stdin) - return self.load_flows(freader) - else: - with open(path, "rb") as f: - freader = FlowReader(f) - return self.load_flows(freader) - except IOError as v: - raise FlowReadException(v.strerror) - - def process_new_request(self, f): - if self.stickycookie_state: - self.stickycookie_state.handle_request(f) - if self.stickyauth_state: - self.stickyauth_state.handle_request(f) - - if self.anticache: - f.request.anticache() - if self.anticomp: - f.request.anticomp() - - if self.server_playback: - pb = self.do_server_playback(f) - if not pb and self.kill_nonreplay: - f.kill(self) - - def process_new_response(self, f): - if self.stickycookie_state: - self.stickycookie_state.handle_response(f) - - def replay_request(self, f, block=False, run_scripthooks=True): - """ - Returns None if successful, or error message if not. - """ - if f.live and run_scripthooks: - return "Can't replay live request." - if f.intercepted: - return "Can't replay while intercepting..." - if f.request.content is None: - return "Can't replay request with missing content..." - if f.request: - f.backup() - f.request.is_replay = True - if "Content-Length" in f.request.headers: - f.request.headers["Content-Length"] = str(len(f.request.content)) - f.response = None - f.error = None - self.process_new_request(f) - rt = RequestReplayThread( - self.server.config, - f, - self.event_queue if run_scripthooks else False, - self.should_exit - ) - rt.start() # pragma: no cover - if block: - rt.join() - - def handle_log(self, l): - self.add_event(l.msg, l.level) - l.reply() - - def handle_clientconnect(self, root_layer): - self.run_script_hook("clientconnect", root_layer) - root_layer.reply() - - def handle_clientdisconnect(self, root_layer): - self.run_script_hook("clientdisconnect", root_layer) - root_layer.reply() - - def handle_serverconnect(self, server_conn): - self.run_script_hook("serverconnect", server_conn) - server_conn.reply() - - def handle_serverdisconnect(self, server_conn): - self.run_script_hook("serverdisconnect", server_conn) - server_conn.reply() - - def handle_next_layer(self, top_layer): - self.run_script_hook("next_layer", top_layer) - top_layer.reply() - - def handle_error(self, f): - self.state.update_flow(f) - self.run_script_hook("error", f) - if self.client_playback: - self.client_playback.clear(f) - f.reply() - return f - - def handle_request(self, f): - if f.live: - app = self.apps.get(f.request) - if app: - err = app.serve( - f, - f.client_conn.wfile, - **{"mitmproxy.master": self} - ) - if err: - self.add_event("Error in wsgi app. %s" % err, "error") - f.reply(Kill) - return - if f not in self.state.flows: # don't add again on replay - self.state.add_flow(f) - self.active_flows.add(f) - self.replacehooks.run(f) - self.setheaders.run(f) - self.process_new_request(f) - self.run_script_hook("request", f) - return f - - def handle_responseheaders(self, f): - try: - if self.stream_large_bodies: - self.stream_large_bodies.run(f, False) - except HttpException: - f.reply(Kill) - return - - self.run_script_hook("responseheaders", f) - - f.reply() - return f - - def handle_response(self, f): - self.active_flows.discard(f) - self.state.update_flow(f) - self.replacehooks.run(f) - self.setheaders.run(f) - self.run_script_hook("response", f) - if self.client_playback: - self.client_playback.clear(f) - self.process_new_response(f) - if self.stream: - self.stream.add(f) - return f - - def handle_intercept(self, f): - self.state.update_flow(f) - - def handle_accept_intercept(self, f): - self.state.update_flow(f) - - def handle_script_change(self, s): - """ - Handle a script whose contents have been changed on the file system. - - Args: - s (script.Script): the changed script - - Returns: - True, if reloading was successful. - False, otherwise. - """ - ok = True - # We deliberately do not want to fail here. - # In the worst case, we have an "empty" script object. - try: - s.unload() - except script.ScriptException as e: - ok = False - self.add_event('Error reloading "{}":\n{}'.format(s.filename, e), 'error') - try: - s.load() - except script.ScriptException as e: - ok = False - self.add_event('Error reloading "{}":\n{}'.format(s.filename, e), 'error') - else: - self.add_event('"{}" reloaded.'.format(s.filename), 'info') - return ok - - def handle_tcp_open(self, flow): - # TODO: This would break mitmproxy currently. - # self.state.add_flow(flow) - self.active_flows.add(flow) - self.run_script_hook("tcp_open", flow) - flow.reply() - - def handle_tcp_message(self, flow): - self.run_script_hook("tcp_message", flow) - message = flow.messages[-1] - direction = "->" if message.from_client else "<-" - self.add_event("{client} {direction} tcp {direction} {server}".format( - client=repr(flow.client_conn.address), - server=repr(flow.server_conn.address), - direction=direction, - ), "info") - self.add_event(clean_bin(message.content), "debug") - flow.reply() - - def handle_tcp_error(self, flow): - self.add_event("Error in TCP connection to {}: {}".format( - repr(flow.server_conn.address), - flow.error - ), "info") - self.run_script_hook("tcp_error", flow) - flow.reply() - - def handle_tcp_close(self, flow): - self.active_flows.discard(flow) - if self.stream: - self.stream.add(flow) - self.run_script_hook("tcp_close", flow) - flow.reply() - - def shutdown(self): - super(FlowMaster, self).shutdown() - - # Add all flows that are still active - if self.stream: - for flow in self.active_flows: - self.stream.add(flow) - self.stop_stream() - - self.unload_scripts() - - def start_stream(self, fp, filt): - self.stream = FilteredFlowWriter(fp, filt) - - def stop_stream(self): - self.stream.fo.close() - self.stream = None - - def start_stream_to_path(self, path, mode="wb", filt=None): - path = os.path.expanduser(path) - try: - f = open(path, mode) - self.start_stream(f, filt) - except IOError as v: - return str(v) - self.stream_path = path - - -def read_flows_from_paths(paths): - """ - Given a list of filepaths, read all flows and return a list of them. - From a performance perspective, streaming would be advisable - - however, if there's an error with one of the files, we want it to be raised immediately. - - Raises: - FlowReadException, if any error occurs. - """ - try: - flows = [] - for path in paths: - path = os.path.expanduser(path) - with open(path, "rb") as f: - flows.extend(FlowReader(f).stream()) - except IOError as e: - raise FlowReadException(e.strerror) - return flows - - -class FlowWriter: - - def __init__(self, fo): - self.fo = fo - - def add(self, flow): - d = flow.get_state() - tnetstring.dump(d, self.fo) - - -class FlowReader: - - def __init__(self, fo): - self.fo = fo - - def stream(self): - """ - Yields Flow objects from the dump. - """ - - # There is a weird mingw bug that breaks .tell() when reading from stdin. - try: - self.fo.tell() - except IOError: # pragma: no cover - can_tell = False - else: - can_tell = True - - off = 0 - try: - while True: - data = tnetstring.load(self.fo) - try: - data = flow_format_compat.migrate_flow(data) - except ValueError as e: - raise FlowReadException(str(e)) - if can_tell: - off = self.fo.tell() - if data["type"] not in FLOW_TYPES: - raise FlowReadException("Unknown flow type: {}".format(data["type"])) - yield FLOW_TYPES[data["type"]].from_state(data) - except ValueError: - # Error is due to EOF - if can_tell and self.fo.tell() == off and self.fo.read() == '': - return - raise FlowReadException("Invalid data format.") - - -class FilteredFlowWriter: - - def __init__(self, fo, filt): - self.fo = fo - self.filt = filt - - def add(self, f): - if self.filt and not f.match(self.filt): - return - d = f.get_state() - tnetstring.dump(d, self.fo) diff --git a/mitmproxy/flow/__init__.py b/mitmproxy/flow/__init__.py new file mode 100644 index 00000000..c14a0fec --- /dev/null +++ b/mitmproxy/flow/__init__.py @@ -0,0 +1,21 @@ +from __future__ import absolute_import, print_function, division + +from mitmproxy.flow import export, modules +from mitmproxy.flow.io import FlowWriter, FilteredFlowWriter, FlowReader, read_flows_from_paths +from mitmproxy.flow.master import FlowMaster +from mitmproxy.flow.modules import ( + AppRegistry, ReplaceHooks, SetHeaders, StreamLargeBodies, ClientPlaybackState, + ServerPlaybackState, StickyCookieState, StickyAuthState +) +from mitmproxy.flow.state import State, FlowView + +# TODO: We may want to remove the imports from .modules and just expose "modules" + +__all__ = [ + "export", "modules", + "FlowWriter", "FilteredFlowWriter", "FlowReader", "read_flows_from_paths", + "FlowMaster", + "AppRegistry", "ReplaceHooks", "SetHeaders", "StreamLargeBodies", "ClientPlaybackState", + "ServerPlaybackState", "StickyCookieState", "StickyAuthState", + "State", "FlowView", +] diff --git a/mitmproxy/flow_export.py b/mitmproxy/flow/export.py index ae282fce..f0ac02ab 100644 --- a/mitmproxy/flow_export.py +++ b/mitmproxy/flow/export.py @@ -1,11 +1,20 @@ +from __future__ import absolute_import, print_function, division + import json +import re from textwrap import dedent +from six.moves.urllib.parse import quote, quote_plus + import netlib.http -from netlib.utils import parse_content_type -import re -from six.moves.urllib.parse import urlparse, quote, quote_plus + +def dictstr(items, indent): + lines = [] + for k, v in items: + lines.append(indent + "%s: %s,\n" % (repr(k), repr(v))) + return "{\n%s}\n" % "".join(lines) + def curl_command(flow): data = "curl " @@ -45,24 +54,19 @@ def python_code(flow): args = "" headers = "" if flow.request.headers: - lines = [" '%s': '%s',\n" % (k, v) for k, v in flow.request.headers.fields] - headers += "\nheaders = {\n%s}\n" % "".join(lines) + headers += "\nheaders = %s\n" % dictstr(flow.request.headers.fields, " ") args += "\n headers=headers," params = "" if flow.request.query: - lines = [" %s: %s,\n" % (repr(k), repr(v)) for k, v in flow.request.query.to_dict().items()] - params = "\nparams = {\n%s}\n" % "".join(lines) + params = "\nparams = %s\n" % dictstr(flow.request.query.collect(), " ") args += "\n params=params," data = "" if flow.request.body: json_obj = is_json(flow.request.headers, flow.request.body) if json_obj: - # Without the separators field json.dumps() produces - # trailing white spaces: https://bugs.python.org/issue16333 - data = json.dumps(json_obj, indent=4, separators=(',', ': ')) - data = "\njson = %s\n" % data + data = "\njson = %s\n" % dictstr(sorted(json_obj.items()), " ") args += "\n json=json," else: data = "\ndata = '''%s'''\n" % flow.request.body @@ -76,7 +80,6 @@ def python_code(flow): method=flow.request.method, args=args, ) - return code @@ -87,7 +90,7 @@ def raw_request(flow): def is_json(headers, content): if headers: - ct = parse_content_type(headers.get("content-type", "")) + ct = netlib.http.parse_content_type(headers.get("content-type", "")) if ct and "%s/%s" % (ct[0], ct[1]) == "application/json": try: return json.loads(content) @@ -123,24 +126,24 @@ def locust_code(flow): max_wait = 3000 """).strip() - components = map(lambda x: quote(x, safe=""), flow.request.path_components) file_name = "_".join(components) name = re.sub('\W|^(?=\d)', '_', file_name) url = flow.request.scheme + "://" + flow.request.host + "/" + "/".join(components) if name == "" or name is None: - new_name = "_".join([str(flow.request.host) , str(flow.request.timestamp_start)]) + new_name = "_".join([str(flow.request.host), str(flow.request.timestamp_start)]) name = re.sub('\W|^(?=\d)', '_', new_name) args = "" headers = "" if flow.request.headers: - lines = [" '%s': '%s',\n" % (k, v) for k, v in flow.request.headers.fields if k.lower() not in ["host", "cookie"]] + lines = [(k, v) for k, v in flow.request.headers.fields if k.lower() not in ["host", "cookie"]] + lines = [" '%s': '%s',\n" % (k, v) for k, v in lines] headers += "\n headers = {\n%s }\n" % "".join(lines) args += "\n headers=headers," params = "" if flow.request.query: - lines = [" %s: %s,\n" % (repr(k), repr(v)) for k, v in flow.request.query.to_dict().items()] + lines = [" %s: %s,\n" % (repr(k), repr(v)) for k, v in flow.request.query.collect()] params = "\n params = {\n%s }\n" % "".join(lines) args += "\n params=params," diff --git a/mitmproxy/flow/io.py b/mitmproxy/flow/io.py new file mode 100644 index 00000000..cd3d9986 --- /dev/null +++ b/mitmproxy/flow/io.py @@ -0,0 +1,86 @@ +from __future__ import absolute_import, print_function, division + +import os + +from mitmproxy import exceptions +from mitmproxy import models +from mitmproxy import tnetstring +from mitmproxy.flow import io_compat + + +class FlowWriter: + def __init__(self, fo): + self.fo = fo + + def add(self, flow): + d = flow.get_state() + tnetstring.dump(d, self.fo) + + +class FlowReader: + def __init__(self, fo): + self.fo = fo + + def stream(self): + """ + Yields Flow objects from the dump. + """ + + # There is a weird mingw bug that breaks .tell() when reading from stdin. + try: + self.fo.tell() + except IOError: # pragma: no cover + can_tell = False + else: + can_tell = True + + off = 0 + try: + while True: + data = tnetstring.load(self.fo) + try: + data = io_compat.migrate_flow(data) + except ValueError as e: + raise exceptions.FlowReadException(str(e)) + if can_tell: + off = self.fo.tell() + if data["type"] not in models.FLOW_TYPES: + raise exceptions.FlowReadException("Unknown flow type: {}".format(data["type"])) + yield models.FLOW_TYPES[data["type"]].from_state(data) + except ValueError: + # Error is due to EOF + if can_tell and self.fo.tell() == off and self.fo.read() == '': + return + raise exceptions.FlowReadException("Invalid data format.") + + +class FilteredFlowWriter: + def __init__(self, fo, filt): + self.fo = fo + self.filt = filt + + def add(self, f): + if self.filt and not f.match(self.filt): + return + d = f.get_state() + tnetstring.dump(d, self.fo) + + +def read_flows_from_paths(paths): + """ + Given a list of filepaths, read all flows and return a list of them. + From a performance perspective, streaming would be advisable - + however, if there's an error with one of the files, we want it to be raised immediately. + + Raises: + FlowReadException, if any error occurs. + """ + try: + flows = [] + for path in paths: + path = os.path.expanduser(path) + with open(path, "rb") as f: + flows.extend(FlowReader(f).stream()) + except IOError as e: + raise exceptions.FlowReadException(e.strerror) + return flows diff --git a/mitmproxy/flow_format_compat.py b/mitmproxy/flow/io_compat.py index 8478367c..7522163f 100644 --- a/mitmproxy/flow_format_compat.py +++ b/mitmproxy/flow/io_compat.py @@ -2,7 +2,8 @@ This module handles the import of mitmproxy flows generated by old versions. """ from __future__ import absolute_import, print_function, division -from . import version + +from mitmproxy import version def convert_013_014(data): @@ -64,5 +65,7 @@ def migrate_flow(flow_data): flow_data = converters[flow_version](flow_data) else: v = ".".join(str(i) for i in flow_data["version"]) - raise ValueError("Incompatible serialized data version: {}".format(v)) + raise ValueError( + "{} cannot read files serialized with version {}.".format(version.NAMEVERSION, v) + ) return flow_data diff --git a/mitmproxy/flow/master.py b/mitmproxy/flow/master.py new file mode 100644 index 00000000..ec0bf36d --- /dev/null +++ b/mitmproxy/flow/master.py @@ -0,0 +1,544 @@ +from __future__ import absolute_import, print_function, division + +import os +import sys + +from typing import List, Optional, Set # noqa + +import netlib.exceptions +from mitmproxy import controller +from mitmproxy import exceptions +from mitmproxy import filt +from mitmproxy import models +from mitmproxy import script +from mitmproxy.flow import io +from mitmproxy.flow import modules +from mitmproxy.onboarding import app +from mitmproxy.protocol import http_replay +from mitmproxy.proxy.config import HostMatcher +from netlib import strutils + + +class FlowMaster(controller.Master): + + @property + def server(self): + # At some point, we may want to have support for multiple servers. + # For now, this suffices. + if len(self.servers) > 0: + return self.servers[0] + + def __init__(self, server, state): + super(FlowMaster, self).__init__() + if server: + self.add_server(server) + self.state = state + self.active_flows = set() # type: Set[models.Flow] + self.server_playback = None # type: Optional[modules.ServerPlaybackState] + self.client_playback = None # type: Optional[modules.ClientPlaybackState] + self.kill_nonreplay = False + self.scripts = [] # type: List[script.Script] + self.pause_scripts = False + + self.stickycookie_state = None # type: Optional[modules.StickyCookieState] + self.stickycookie_txt = None + + self.stickyauth_state = False # type: Optional[modules.StickyAuthState] + self.stickyauth_txt = None + + self.anticache = False + self.anticomp = False + self.stream_large_bodies = None # type: Optional[modules.StreamLargeBodies] + self.refresh_server_playback = False + self.replacehooks = modules.ReplaceHooks() + self.setheaders = modules.SetHeaders() + self.replay_ignore_params = False + self.replay_ignore_content = None + self.replay_ignore_host = False + + self.stream = None + self.apps = modules.AppRegistry() + + def start_app(self, host, port): + self.apps.add( + app.mapp, + host, + port + ) + + def add_event(self, e, level="info"): + """ + level: debug, info, error + """ + + def unload_scripts(self): + for s in self.scripts[:]: + self.unload_script(s) + + def unload_script(self, script_obj): + try: + script_obj.unload() + except script.ScriptException as e: + self.add_event("Script error:\n" + str(e), "error") + script.reloader.unwatch(script_obj) + self.scripts.remove(script_obj) + + def load_script(self, command, use_reloader=False): + """ + Loads a script. + + Raises: + ScriptException + """ + s = script.Script(command, script.ScriptContext(self)) + s.load() + if use_reloader: + script.reloader.watch(s, lambda: self.event_queue.put(("script_change", s))) + self.scripts.append(s) + + def _run_single_script_hook(self, script_obj, name, *args, **kwargs): + if script_obj and not self.pause_scripts: + try: + script_obj.run(name, *args, **kwargs) + except script.ScriptException as e: + self.add_event("Script error:\n{}".format(e), "error") + + def run_script_hook(self, name, *args, **kwargs): + for script_obj in self.scripts: + self._run_single_script_hook(script_obj, name, *args, **kwargs) + + def get_ignore_filter(self): + return self.server.config.check_ignore.patterns + + def set_ignore_filter(self, host_patterns): + self.server.config.check_ignore = HostMatcher(host_patterns) + + def get_tcp_filter(self): + return self.server.config.check_tcp.patterns + + def set_tcp_filter(self, host_patterns): + self.server.config.check_tcp = HostMatcher(host_patterns) + + def set_stickycookie(self, txt): + if txt: + flt = filt.parse(txt) + if not flt: + return "Invalid filter expression." + self.stickycookie_state = modules.StickyCookieState(flt) + self.stickycookie_txt = txt + else: + self.stickycookie_state = None + self.stickycookie_txt = None + + def set_stream_large_bodies(self, max_size): + if max_size is not None: + self.stream_large_bodies = modules.StreamLargeBodies(max_size) + else: + self.stream_large_bodies = False + + def set_stickyauth(self, txt): + if txt: + flt = filt.parse(txt) + if not flt: + return "Invalid filter expression." + self.stickyauth_state = modules.StickyAuthState(flt) + self.stickyauth_txt = txt + else: + self.stickyauth_state = None + self.stickyauth_txt = None + + def start_client_playback(self, flows, exit): + """ + flows: List of flows. + """ + self.client_playback = modules.ClientPlaybackState(flows, exit) + + def stop_client_playback(self): + self.client_playback = None + + def start_server_playback( + self, + flows, + kill, + headers, + exit, + nopop, + ignore_params, + ignore_content, + ignore_payload_params, + ignore_host): + """ + flows: List of flows. + kill: Boolean, should we kill requests not part of the replay? + ignore_params: list of parameters to ignore in server replay + ignore_content: true if request content should be ignored in server replay + ignore_payload_params: list of content params to ignore in server replay + ignore_host: true if request host should be ignored in server replay + """ + self.server_playback = modules.ServerPlaybackState( + headers, + flows, + exit, + nopop, + ignore_params, + ignore_content, + ignore_payload_params, + ignore_host) + self.kill_nonreplay = kill + + def stop_server_playback(self): + self.server_playback = None + + def do_server_playback(self, flow): + """ + This method should be called by child classes in the request + handler. Returns True if playback has taken place, None if not. + """ + if self.server_playback: + rflow = self.server_playback.next_flow(flow) + if not rflow: + return None + response = rflow.response.copy() + response.is_replay = True + if self.refresh_server_playback: + response.refresh() + flow.response = response + return True + return None + + def tick(self, timeout): + if self.client_playback: + stop = ( + self.client_playback.done() and + self.state.active_flow_count() == 0 + ) + exit = self.client_playback.exit + if stop: + self.stop_client_playback() + if exit: + self.shutdown() + else: + self.client_playback.tick(self) + + if self.server_playback: + stop = ( + self.server_playback.count() == 0 and + self.state.active_flow_count() == 0 and + not self.kill_nonreplay + ) + exit = self.server_playback.exit + if stop: + self.stop_server_playback() + if exit: + self.shutdown() + return super(FlowMaster, self).tick(timeout) + + def duplicate_flow(self, f): + f2 = f.copy() + self.load_flow(f2) + return f2 + + def create_request(self, method, scheme, host, port, path): + """ + this method creates a new artificial and minimalist request also adds it to flowlist + """ + c = models.ClientConnection.make_dummy(("", 0)) + s = models.ServerConnection.make_dummy((host, port)) + + f = models.HTTPFlow(c, s) + headers = models.Headers() + + req = models.HTTPRequest( + "absolute", + method, + scheme, + host, + port, + path, + b"HTTP/1.1", + headers, + b"" + ) + f.request = req + self.load_flow(f) + return f + + def load_flow(self, f): + """ + Loads a flow + """ + if isinstance(f, models.HTTPFlow): + if self.server and self.server.config.mode == "reverse": + f.request.host = self.server.config.upstream_server.address.host + f.request.port = self.server.config.upstream_server.address.port + f.request.scheme = self.server.config.upstream_server.scheme + + f.reply = controller.DummyReply() + if f.request: + self.request(f) + if f.response: + self.responseheaders(f) + self.response(f) + if f.error: + self.error(f) + elif isinstance(f, models.TCPFlow): + messages = f.messages + f.messages = [] + f.reply = controller.DummyReply() + self.tcp_open(f) + while messages: + f.messages.append(messages.pop(0)) + self.tcp_message(f) + if f.error: + self.tcp_error(f) + self.tcp_close(f) + else: + raise NotImplementedError() + + def load_flows(self, fr): + """ + Load flows from a FlowReader object. + """ + cnt = 0 + for i in fr.stream(): + cnt += 1 + self.load_flow(i) + return cnt + + def load_flows_file(self, path): + path = os.path.expanduser(path) + try: + if path == "-": + # This is incompatible with Python 3 - maybe we can use click? + freader = io.FlowReader(sys.stdin) + return self.load_flows(freader) + else: + with open(path, "rb") as f: + freader = io.FlowReader(f) + return self.load_flows(freader) + except IOError as v: + raise exceptions.FlowReadException(v.strerror) + + def process_new_request(self, f): + if self.stickycookie_state: + self.stickycookie_state.handle_request(f) + if self.stickyauth_state: + self.stickyauth_state.handle_request(f) + + if self.anticache: + f.request.anticache() + if self.anticomp: + f.request.anticomp() + + if self.server_playback: + pb = self.do_server_playback(f) + if not pb and self.kill_nonreplay: + f.kill(self) + + def process_new_response(self, f): + if self.stickycookie_state: + self.stickycookie_state.handle_response(f) + + def replay_request(self, f, block=False, run_scripthooks=True): + """ + Returns None if successful, or error message if not. + """ + if f.live and run_scripthooks: + return "Can't replay live request." + if f.intercepted: + return "Can't replay while intercepting..." + if f.request.content is None: + return "Can't replay request with missing content..." + if f.request: + f.backup() + f.request.is_replay = True + if "Content-Length" in f.request.headers: + f.request.headers["Content-Length"] = str(len(f.request.content)) + f.response = None + f.error = None + self.process_new_request(f) + rt = http_replay.RequestReplayThread( + self.server.config, + f, + self.event_queue if run_scripthooks else False, + self.should_exit + ) + rt.start() # pragma: no cover + if block: + rt.join() + + @controller.handler + def log(self, l): + self.add_event(l.msg, l.level) + + @controller.handler + def clientconnect(self, root_layer): + self.run_script_hook("clientconnect", root_layer) + + @controller.handler + def clientdisconnect(self, root_layer): + self.run_script_hook("clientdisconnect", root_layer) + + @controller.handler + def serverconnect(self, server_conn): + self.run_script_hook("serverconnect", server_conn) + + @controller.handler + def serverdisconnect(self, server_conn): + self.run_script_hook("serverdisconnect", server_conn) + + @controller.handler + def next_layer(self, top_layer): + self.run_script_hook("next_layer", top_layer) + + @controller.handler + def error(self, f): + self.state.update_flow(f) + self.run_script_hook("error", f) + if self.client_playback: + self.client_playback.clear(f) + return f + + @controller.handler + def request(self, f): + if f.live: + app = self.apps.get(f.request) + if app: + err = app.serve( + f, + f.client_conn.wfile, + **{"mitmproxy.master": self} + ) + if err: + self.add_event("Error in wsgi app. %s" % err, "error") + f.reply(exceptions.Kill) + return + if f not in self.state.flows: # don't add again on replay + self.state.add_flow(f) + self.active_flows.add(f) + self.replacehooks.run(f) + self.setheaders.run(f) + self.process_new_request(f) + self.run_script_hook("request", f) + return f + + @controller.handler + def responseheaders(self, f): + try: + if self.stream_large_bodies: + self.stream_large_bodies.run(f, False) + except netlib.exceptions.HttpException: + f.reply(exceptions.Kill) + return + self.run_script_hook("responseheaders", f) + return f + + @controller.handler + def response(self, f): + self.active_flows.discard(f) + self.state.update_flow(f) + self.replacehooks.run(f) + self.setheaders.run(f) + self.run_script_hook("response", f) + if self.client_playback: + self.client_playback.clear(f) + self.process_new_response(f) + if self.stream: + self.stream.add(f) + return f + + def handle_intercept(self, f): + self.state.update_flow(f) + + def handle_accept_intercept(self, f): + self.state.update_flow(f) + + @controller.handler + def script_change(self, s): + """ + Handle a script whose contents have been changed on the file system. + + Args: + s (script.Script): the changed script + + Returns: + True, if reloading was successful. + False, otherwise. + """ + ok = True + # We deliberately do not want to fail here. + # In the worst case, we have an "empty" script object. + try: + s.unload() + except script.ScriptException as e: + ok = False + self.add_event('Error reloading "{}":\n{}'.format(s.filename, e), 'error') + try: + s.load() + except script.ScriptException as e: + ok = False + self.add_event('Error reloading "{}":\n{}'.format(s.filename, e), 'error') + else: + self.add_event('"{}" reloaded.'.format(s.filename), 'info') + return ok + + @controller.handler + def tcp_open(self, flow): + # TODO: This would break mitmproxy currently. + # self.state.add_flow(flow) + self.active_flows.add(flow) + self.run_script_hook("tcp_open", flow) + + @controller.handler + def tcp_message(self, flow): + self.run_script_hook("tcp_message", flow) + message = flow.messages[-1] + direction = "->" if message.from_client else "<-" + self.add_event("{client} {direction} tcp {direction} {server}".format( + client=repr(flow.client_conn.address), + server=repr(flow.server_conn.address), + direction=direction, + ), "info") + self.add_event(strutils.clean_bin(message.content), "debug") + + @controller.handler + def tcp_error(self, flow): + self.add_event("Error in TCP connection to {}: {}".format( + repr(flow.server_conn.address), + flow.error + ), "info") + self.run_script_hook("tcp_error", flow) + + @controller.handler + def tcp_close(self, flow): + self.active_flows.discard(flow) + if self.stream: + self.stream.add(flow) + self.run_script_hook("tcp_close", flow) + + def shutdown(self): + super(FlowMaster, self).shutdown() + + # Add all flows that are still active + if self.stream: + for flow in self.active_flows: + self.stream.add(flow) + self.stop_stream() + + self.unload_scripts() + + def start_stream(self, fp, filt): + self.stream = io.FilteredFlowWriter(fp, filt) + + def stop_stream(self): + self.stream.fo.close() + self.stream = None + + def start_stream_to_path(self, path, mode="wb", filt=None): + path = os.path.expanduser(path) + try: + f = open(path, mode) + self.start_stream(f, filt) + except IOError as v: + return str(v) + self.stream_path = path diff --git a/mitmproxy/flow/modules.py b/mitmproxy/flow/modules.py new file mode 100644 index 00000000..601ebfce --- /dev/null +++ b/mitmproxy/flow/modules.py @@ -0,0 +1,358 @@ +from __future__ import absolute_import, print_function, division + +import collections +import hashlib +import re + +from six.moves import http_cookiejar +from six.moves import urllib + +from mitmproxy import controller +from mitmproxy import filt +from mitmproxy import version +from netlib import wsgi +from netlib.http import cookies +from netlib.http import http1 + + +class AppRegistry: + def __init__(self): + self.apps = {} + + def add(self, app, domain, port): + """ + Add a WSGI app to the registry, to be served for requests to the + specified domain, on the specified port. + """ + self.apps[(domain, port)] = wsgi.WSGIAdaptor( + app, + domain, + port, + version.NAMEVERSION + ) + + def get(self, request): + """ + Returns an WSGIAdaptor instance if request matches an app, or None. + """ + if (request.host, request.port) in self.apps: + return self.apps[(request.host, request.port)] + if "host" in request.headers: + host = request.headers["host"] + return self.apps.get((host, request.port), None) + + +class ReplaceHooks: + def __init__(self): + self.lst = [] + + def set(self, r): + self.clear() + for i in r: + self.add(*i) + + def add(self, fpatt, rex, s): + """ + add a replacement hook. + + fpatt: a string specifying a filter pattern. + rex: a regular expression. + s: the replacement string + + returns true if hook was added, false if the pattern could not be + parsed. + """ + cpatt = filt.parse(fpatt) + if not cpatt: + return False + try: + re.compile(rex) + except re.error: + return False + self.lst.append((fpatt, rex, s, cpatt)) + return True + + def get_specs(self): + """ + Retrieve the hook specifcations. Returns a list of (fpatt, rex, s) + tuples. + """ + return [i[:3] for i in self.lst] + + def count(self): + return len(self.lst) + + def run(self, f): + for _, rex, s, cpatt in self.lst: + if cpatt(f): + if f.response: + f.response.replace(rex, s) + else: + f.request.replace(rex, s) + + def clear(self): + self.lst = [] + + +class SetHeaders: + def __init__(self): + self.lst = [] + + def set(self, r): + self.clear() + for i in r: + self.add(*i) + + def add(self, fpatt, header, value): + """ + Add a set header hook. + + fpatt: String specifying a filter pattern. + header: Header name. + value: Header value string + + Returns True if hook was added, False if the pattern could not be + parsed. + """ + cpatt = filt.parse(fpatt) + if not cpatt: + return False + self.lst.append((fpatt, header, value, cpatt)) + return True + + def get_specs(self): + """ + Retrieve the hook specifcations. Returns a list of (fpatt, rex, s) + tuples. + """ + return [i[:3] for i in self.lst] + + def count(self): + return len(self.lst) + + def clear(self): + self.lst = [] + + def run(self, f): + for _, header, value, cpatt in self.lst: + if cpatt(f): + if f.response: + f.response.headers.pop(header, None) + else: + f.request.headers.pop(header, None) + for _, header, value, cpatt in self.lst: + if cpatt(f): + if f.response: + f.response.headers.add(header, value) + else: + f.request.headers.add(header, value) + + +class StreamLargeBodies(object): + def __init__(self, max_size): + self.max_size = max_size + + def run(self, flow, is_request): + r = flow.request if is_request else flow.response + expected_size = http1.expected_http_body_size( + flow.request, flow.response if not is_request else None + ) + if not r.content and not (0 <= expected_size <= self.max_size): + # r.stream may already be a callable, which we want to preserve. + r.stream = r.stream or True + + +class ClientPlaybackState: + def __init__(self, flows, exit): + self.flows, self.exit = flows, exit + self.current = None + self.testing = False # Disables actual replay for testing. + + def count(self): + return len(self.flows) + + def done(self): + if len(self.flows) == 0 and not self.current: + return True + return False + + def clear(self, flow): + """ + A request has returned in some way - if this is the one we're + servicing, go to the next flow. + """ + if flow is self.current: + self.current = None + + def tick(self, master): + if self.flows and not self.current: + self.current = self.flows.pop(0).copy() + if not self.testing: + master.replay_request(self.current) + else: + self.current.reply = controller.DummyReply() + master.request(self.current) + if self.current.response: + master.response(self.current) + + +class ServerPlaybackState: + def __init__( + self, + headers, + flows, + exit, + nopop, + ignore_params, + ignore_content, + ignore_payload_params, + ignore_host): + """ + headers: Case-insensitive list of request headers that should be + included in request-response matching. + """ + self.headers = headers + self.exit = exit + self.nopop = nopop + self.ignore_params = ignore_params + self.ignore_content = ignore_content + self.ignore_payload_params = ignore_payload_params + self.ignore_host = ignore_host + self.fmap = {} + for i in flows: + if i.response: + l = self.fmap.setdefault(self._hash(i), []) + l.append(i) + + def count(self): + return sum(len(i) for i in self.fmap.values()) + + def _hash(self, flow): + """ + Calculates a loose hash of the flow request. + """ + r = flow.request + + _, _, path, _, query, _ = urllib.parse.urlparse(r.url) + queriesArray = urllib.parse.parse_qsl(query, keep_blank_values=True) + + key = [ + str(r.port), + str(r.scheme), + str(r.method), + str(path), + ] + + if not self.ignore_content: + form_contents = r.urlencoded_form or r.multipart_form + if self.ignore_payload_params and form_contents: + key.extend( + p for p in form_contents.items(multi=True) + if p[0] not in self.ignore_payload_params + ) + else: + key.append(str(r.content)) + + if not self.ignore_host: + key.append(r.host) + + filtered = [] + ignore_params = self.ignore_params or [] + for p in queriesArray: + if p[0] not in ignore_params: + filtered.append(p) + for p in filtered: + key.append(p[0]) + key.append(p[1]) + + if self.headers: + headers = [] + for i in self.headers: + v = r.headers.get(i) + headers.append((i, v)) + key.append(headers) + return hashlib.sha256(repr(key)).digest() + + def next_flow(self, request): + """ + Returns the next flow object, or None if no matching flow was + found. + """ + l = self.fmap.get(self._hash(request)) + if not l: + return None + + if self.nopop: + return l[0] + else: + return l.pop(0) + + +class StickyCookieState: + def __init__(self, flt): + """ + flt: Compiled filter. + """ + self.jar = collections.defaultdict(dict) + self.flt = flt + + def ckey(self, attrs, f): + """ + Returns a (domain, port, path) tuple. + """ + domain = f.request.host + path = "/" + if "domain" in attrs: + domain = attrs["domain"] + if "path" in attrs: + path = attrs["path"] + return (domain, f.request.port, path) + + def domain_match(self, a, b): + if http_cookiejar.domain_match(a, b): + return True + elif http_cookiejar.domain_match(a, b.strip(".")): + return True + return False + + def handle_response(self, f): + for name, (value, attrs) in f.response.cookies.items(multi=True): + # FIXME: We now know that Cookie.py screws up some cookies with + # valid RFC 822/1123 datetime specifications for expiry. Sigh. + a = self.ckey(attrs, f) + if self.domain_match(f.request.host, a[0]): + b = attrs.with_insert(0, name, value) + self.jar[a][name] = b + + def handle_request(self, f): + l = [] + if f.match(self.flt): + for domain, port, path in self.jar.keys(): + match = [ + self.domain_match(f.request.host, domain), + f.request.port == port, + f.request.path.startswith(path) + ] + if all(match): + c = self.jar[(domain, port, path)] + l.extend([cookies.format_cookie_header(c[name].items(multi=True)) for name in c.keys()]) + if l: + f.request.stickycookie = True + f.request.headers["cookie"] = "; ".join(l) + + +class StickyAuthState: + def __init__(self, flt): + """ + flt: Compiled filter. + """ + self.flt = flt + self.hosts = {} + + def handle_request(self, f): + host = f.request.host + if "authorization" in f.request.headers: + self.hosts[host] = f.request.headers["authorization"] + elif f.match(self.flt): + if host in self.hosts: + f.request.headers["authorization"] = self.hosts[host] diff --git a/mitmproxy/flow/state.py b/mitmproxy/flow/state.py new file mode 100644 index 00000000..4287d77b --- /dev/null +++ b/mitmproxy/flow/state.py @@ -0,0 +1,269 @@ +from __future__ import absolute_import, print_function, division + +from abc import abstractmethod, ABCMeta + +import six +from typing import List # noqa + +from mitmproxy import filt +from mitmproxy import models # noqa + + +@six.add_metaclass(ABCMeta) +class FlowList(object): + def __init__(self): + self._list = [] # type: List[models.Flow] + + def __iter__(self): + return iter(self._list) + + def __contains__(self, item): + return item in self._list + + def __getitem__(self, item): + return self._list[item] + + def __bool__(self): + return bool(self._list) + + if six.PY2: + __nonzero__ = __bool__ + + def __len__(self): + return len(self._list) + + def index(self, f): + return self._list.index(f) + + @abstractmethod + def _add(self, f): + return + + @abstractmethod + def _update(self, f): + return + + @abstractmethod + def _remove(self, f): + return + + +def _pos(*args): + return True + + +class FlowView(FlowList): + def __init__(self, store, filt=None): + super(FlowView, self).__init__() + if not filt: + filt = _pos + self._build(store, filt) + + self.store = store + self.store.views.append(self) + + def _close(self): + self.store.views.remove(self) + + def _build(self, flows, filt=None): + if filt: + self.filt = filt + self._list = list(filter(self.filt, flows)) + + def _add(self, f): + if self.filt(f): + self._list.append(f) + + def _update(self, f): + if f not in self._list: + self._add(f) + elif not self.filt(f): + self._remove(f) + + def _remove(self, f): + if f in self._list: + self._list.remove(f) + + def _recalculate(self, flows): + self._build(flows) + + +class FlowStore(FlowList): + """ + Responsible for handling flows in the state: + Keeps a list of all flows and provides views on them. + """ + + def __init__(self): + super(FlowStore, self).__init__() + self._set = set() # Used for O(1) lookups + self.views = [] + self._recalculate_views() + + def get(self, flow_id): + for f in self._list: + if f.id == flow_id: + return f + + def __contains__(self, f): + return f in self._set + + def _add(self, f): + """ + Adds a flow to the state. + The flow to add must not be present in the state. + """ + self._list.append(f) + self._set.add(f) + for view in self.views: + view._add(f) + + def _update(self, f): + """ + Notifies the state that a flow has been updated. + The flow must be present in the state. + """ + if f in self: + for view in self.views: + view._update(f) + + def _remove(self, f): + """ + Deletes a flow from the state. + The flow must be present in the state. + """ + self._list.remove(f) + self._set.remove(f) + for view in self.views: + view._remove(f) + + # Expensive bulk operations + + def _extend(self, flows): + """ + Adds a list of flows to the state. + The list of flows to add must not contain flows that are already in the state. + """ + self._list.extend(flows) + self._set.update(flows) + self._recalculate_views() + + def _clear(self): + self._list = [] + self._set = set() + self._recalculate_views() + + def _recalculate_views(self): + """ + Expensive operation: Recalculate all the views after a bulk change. + """ + for view in self.views: + view._recalculate(self) + + # Utility functions. + # There are some common cases where we need to argue about all flows + # irrespective of filters on the view etc (i.e. on shutdown). + + def active_count(self): + c = 0 + for i in self._list: + if not i.response and not i.error: + c += 1 + return c + + # TODO: Should accept_all operate on views or on all flows? + def accept_all(self, master): + for f in self._list: + f.accept_intercept(master) + + def kill_all(self, master): + for f in self._list: + if not f.reply.acked: + f.kill(master) + + +class State(object): + def __init__(self): + self.flows = FlowStore() + self.view = FlowView(self.flows, None) + + # These are compiled filt expressions: + self.intercept = None + + @property + def limit_txt(self): + return getattr(self.view.filt, "pattern", None) + + def flow_count(self): + return len(self.flows) + + # TODO: All functions regarding flows that don't cause side-effects should + # be moved into FlowStore. + def index(self, f): + return self.flows.index(f) + + def active_flow_count(self): + return self.flows.active_count() + + def add_flow(self, f): + """ + Add a request to the state. + """ + self.flows._add(f) + return f + + def update_flow(self, f): + """ + Add a response to the state. + """ + self.flows._update(f) + return f + + def delete_flow(self, f): + self.flows._remove(f) + + def load_flows(self, flows): + self.flows._extend(flows) + + def set_limit(self, txt): + if txt == self.limit_txt: + return + if txt: + f = filt.parse(txt) + if not f: + return "Invalid filter expression." + self.view._close() + self.view = FlowView(self.flows, f) + else: + self.view._close() + self.view = FlowView(self.flows, None) + + def set_intercept(self, txt): + if txt: + f = filt.parse(txt) + if not f: + return "Invalid filter expression." + self.intercept = f + else: + self.intercept = None + + @property + def intercept_txt(self): + return getattr(self.intercept, "pattern", None) + + def clear(self): + self.flows._clear() + + def accept_all(self, master): + self.flows.accept_all(master) + + def backup(self, f): + f.backup() + self.update_flow(f) + + def revert(self, f): + f.revert() + self.update_flow(f) + + def killall(self, master): + self.flows.kill_all(master) diff --git a/mitmproxy/main.py b/mitmproxy/main.py index d44c257e..bf0b7e4d 100644 --- a/mitmproxy/main.py +++ b/mitmproxy/main.py @@ -1,13 +1,16 @@ -from __future__ import print_function, absolute_import +from __future__ import absolute_import, print_function, division + import os import signal import sys + from six.moves import _thread # PY3: We only need _thread.error, which is an alias of RuntimeError in 3.3+ -from netlib.version_check import check_pyopenssl_version -from . import cmdline -from .exceptions import ServerException -from .proxy.server import DummyServer, ProxyServer -from .proxy.config import process_proxy_options + +from mitmproxy import cmdline +from mitmproxy import exceptions +from mitmproxy.proxy import config +from mitmproxy.proxy import server +from netlib import version_check def assert_utf8_env(): @@ -28,11 +31,11 @@ def assert_utf8_env(): def get_server(dummy_server, options): if dummy_server: - return DummyServer(options) + return server.DummyServer(options) else: try: - return ProxyServer(options) - except ServerException as v: + return server.ProxyServer(options) + except exceptions.ServerException as v: print(str(v), file=sys.stderr) sys.exit(1) @@ -44,7 +47,7 @@ def mitmproxy(args=None): # pragma: no cover sys.exit(1) from . import console - check_pyopenssl_version() + version_check.check_pyopenssl_version() assert_utf8_env() parser = cmdline.mitmproxy() @@ -52,7 +55,7 @@ def mitmproxy(args=None): # pragma: no cover if options.quiet: options.verbose = 0 - proxy_config = process_proxy_options(parser, options) + proxy_config = config.process_proxy_options(parser, options) console_options = console.Options(**cmdline.get_common_options(options)) console_options.palette = options.palette console_options.palette_transparent = options.palette_transparent @@ -74,7 +77,7 @@ def mitmproxy(args=None): # pragma: no cover def mitmdump(args=None): # pragma: no cover from . import dump - check_pyopenssl_version() + version_check.check_pyopenssl_version() parser = cmdline.mitmdump() options = parser.parse_args(args) @@ -82,7 +85,7 @@ def mitmdump(args=None): # pragma: no cover options.verbose = 0 options.flow_detail = 0 - proxy_config = process_proxy_options(parser, options) + proxy_config = config.process_proxy_options(parser, options) dump_options = dump.Options(**cmdline.get_common_options(options)) dump_options.flow_detail = options.flow_detail dump_options.keepserving = options.keepserving @@ -108,7 +111,7 @@ def mitmdump(args=None): # pragma: no cover def mitmweb(args=None): # pragma: no cover from . import web - check_pyopenssl_version() + version_check.check_pyopenssl_version() parser = cmdline.mitmweb() @@ -116,7 +119,7 @@ def mitmweb(args=None): # pragma: no cover if options.quiet: options.verbose = 0 - proxy_config = process_proxy_options(parser, options) + proxy_config = config.process_proxy_options(parser, options) web_options = web.Options(**cmdline.get_common_options(options)) web_options.intercept = options.intercept web_options.wdebug = options.wdebug diff --git a/mitmproxy/models/__init__.py b/mitmproxy/models/__init__.py index 3d9d9dae..ca813567 100644 --- a/mitmproxy/models/__init__.py +++ b/mitmproxy/models/__init__.py @@ -1,12 +1,12 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division +from netlib.http import decoded +from .connections import ClientConnection, ServerConnection +from .flow import Flow, Error from .http import ( HTTPFlow, HTTPRequest, HTTPResponse, Headers, make_error_response, make_connect_request, make_connect_response, expect_continue_response ) -from netlib.http import decoded -from .connections import ClientConnection, ServerConnection -from .flow import Flow, Error from .tcp import TCPFlow FLOW_TYPES = dict( diff --git a/mitmproxy/models/connections.py b/mitmproxy/models/connections.py index 91590bca..6347f488 100644 --- a/mitmproxy/models/connections.py +++ b/mitmproxy/models/connections.py @@ -1,15 +1,18 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division import copy import os import six -from netlib import tcp, certutils -from .. import stateobject, utils +from mitmproxy import stateobject +from mitmproxy import utils +from netlib import certutils +from netlib import tcp class ClientConnection(tcp.BaseHandler, stateobject.StateObject): + """ A client connection @@ -21,6 +24,7 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject): timestamp_ssl_setup: TLS established timestamp timestamp_end: Connection end timestamp """ + def __init__(self, client_connection, address, server): # Eventually, this object is restored from state. We don't have a # connection then. @@ -101,6 +105,7 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject): class ServerConnection(tcp.TCPClient, stateobject.StateObject): + """ A server connection @@ -117,6 +122,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): timestamp_ssl_setup: TLS established timestamp timestamp_end: Connection end timestamp """ + def __init__(self, address, source_address=None): tcp.TCPClient.__init__(self, address, source_address) @@ -182,7 +188,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): timestamp_ssl_setup=None, timestamp_end=None, via=None - )) + )) def copy(self): return copy.copy(self) diff --git a/mitmproxy/models/flow.py b/mitmproxy/models/flow.py index 1019c9fb..7b9ec030 100644 --- a/mitmproxy/models/flow.py +++ b/mitmproxy/models/flow.py @@ -1,10 +1,14 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division + import copy import uuid -from .. import stateobject, utils, version -from .connections import ClientConnection, ServerConnection -from ..exceptions import Kill +from mitmproxy import exceptions +from mitmproxy import stateobject +from mitmproxy import utils +from mitmproxy import version +from mitmproxy.models.connections import ClientConnection +from mitmproxy.models.connections import ServerConnection class Error(stateobject.StateObject): @@ -151,8 +155,8 @@ class Flow(stateobject.StateObject): """ self.error = Error("Connection killed") self.intercepted = False - self.reply(Kill) - master.handle_error(self) + self.reply(exceptions.Kill) + master.error(self) def intercept(self, master): """ diff --git a/mitmproxy/models/http.py b/mitmproxy/models/http.py index 75ffbfd0..a32124ac 100644 --- a/mitmproxy/models/http.py +++ b/mitmproxy/models/http.py @@ -1,11 +1,15 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division + import cgi +from mitmproxy import version +from mitmproxy.models.flow import Flow from netlib import encoding -from netlib.http import status_codes, Headers, Request, Response +from netlib.http import Headers +from netlib.http import Request +from netlib.http import Response +from netlib.http import status_codes from netlib.tcp import Address -from .. import version -from .flow import Flow class MessageMixin(object): @@ -72,9 +76,9 @@ class HTTPRequest(MessageMixin, Request): def get_state(self): state = super(HTTPRequest, self).get_state() state.update( - stickycookie = self.stickycookie, - stickyauth = self.stickyauth, - is_replay = self.is_replay, + stickycookie=self.stickycookie, + stickyauth=self.stickyauth, + is_replay=self.is_replay, ) return state @@ -109,6 +113,7 @@ class HTTPRequest(MessageMixin, Request): class HTTPResponse(MessageMixin, Response): + """ A mitmproxy HTTP response. This is a very thin wrapper on top of :py:class:`netlib.http.Response` and @@ -124,7 +129,7 @@ class HTTPResponse(MessageMixin, Response): content, timestamp_start=None, timestamp_end=None, - is_replay = False + is_replay=False ): Response.__init__( self, diff --git a/mitmproxy/models/tcp.py b/mitmproxy/models/tcp.py index 7e966b95..e33475c2 100644 --- a/mitmproxy/models/tcp.py +++ b/mitmproxy/models/tcp.py @@ -1,11 +1,15 @@ +from __future__ import absolute_import, print_function, division + import time + from typing import List -from netlib.utils import Serializable -from .flow import Flow +import netlib.basetypes +from mitmproxy.models.flow import Flow -class TCPMessage(Serializable): +class TCPMessage(netlib.basetypes.Serializable): + def __init__(self, from_client, content, timestamp=None): self.content = content self.from_client = from_client @@ -33,6 +37,7 @@ class TCPMessage(Serializable): class TCPFlow(Flow): + """ A TCPFlow is a simplified representation of a TCP session. """ diff --git a/mitmproxy/onboarding/app.py b/mitmproxy/onboarding/app.py index ff5ed63c..f93b9982 100644 --- a/mitmproxy/onboarding/app.py +++ b/mitmproxy/onboarding/app.py @@ -1,12 +1,13 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division + import os + +import tornado.template import tornado.web import tornado.wsgi -import tornado.template - -from .. import utils -from ..proxy import config +from mitmproxy import utils +from mitmproxy.proxy import config loader = tornado.template.Loader(utils.pkg_data.path("onboarding/templates")) diff --git a/mitmproxy/platform/osx.py b/mitmproxy/platform/osx.py index 3cd4bc66..b16c1861 100644 --- a/mitmproxy/platform/osx.py +++ b/mitmproxy/platform/osx.py @@ -1,4 +1,5 @@ import subprocess + import pf """ diff --git a/mitmproxy/platform/windows.py b/mitmproxy/platform/windows.py index 0a810908..576516e2 100644 --- a/mitmproxy/platform/windows.py +++ b/mitmproxy/platform/windows.py @@ -1,18 +1,17 @@ -import configargparse -from six.moves import cPickle as pickle -from ctypes import byref, windll, Structure -from ctypes.wintypes import DWORD +import collections +import ctypes +import ctypes.wintypes import os import socket -from six.moves import socketserver import struct import threading import time -from collections import OrderedDict - -from pydivert.windivert import WinDivert -from pydivert.enum import Direction, Layer, Flag +import configargparse +from pydivert import enum +from pydivert import windivert +from six.moves import cPickle as pickle +from six.moves import socketserver PROXY_API_PORT = 8085 @@ -91,22 +90,22 @@ ERROR_INSUFFICIENT_BUFFER = 0x7A # http://msdn.microsoft.com/en-us/library/windows/desktop/bb485761(v=vs.85).aspx -class MIB_TCPROW2(Structure): +class MIB_TCPROW2(ctypes.Structure): _fields_ = [ - ('dwState', DWORD), - ('dwLocalAddr', DWORD), - ('dwLocalPort', DWORD), - ('dwRemoteAddr', DWORD), - ('dwRemotePort', DWORD), - ('dwOwningPid', DWORD), - ('dwOffloadState', DWORD) + ('dwState', ctypes.wintypes.DWORD), + ('dwLocalAddr', ctypes.wintypes.DWORD), + ('dwLocalPort', ctypes.wintypes.DWORD), + ('dwRemoteAddr', ctypes.wintypes.DWORD), + ('dwRemotePort', ctypes.wintypes.DWORD), + ('dwOwningPid', ctypes.wintypes.DWORD), + ('dwOffloadState', ctypes.wintypes.DWORD) ] # http://msdn.microsoft.com/en-us/library/windows/desktop/bb485772(v=vs.85).aspx def MIB_TCPTABLE2(size): - class _MIB_TCPTABLE2(Structure): - _fields_ = [('dwNumEntries', DWORD), + class _MIB_TCPTABLE2(ctypes.Structure): + _fields_ = [('dwNumEntries', ctypes.wintypes.DWORD), ('table', MIB_TCPROW2 * size)] return _MIB_TCPTABLE2() @@ -192,13 +191,13 @@ class TransparentProxy(object): self.proxy_addr, self.proxy_port = proxy_addr, proxy_port self.connection_cache_size = cache_size - self.client_server_map = OrderedDict() + self.client_server_map = collections.OrderedDict() self.api = APIServer(self, (api_host, api_port), APIRequestHandler) self.api_thread = threading.Thread(target=self.api.serve_forever) self.api_thread.daemon = True - self.driver = WinDivert() + self.driver = windivert.WinDivert() self.driver.register() self.request_filter = custom_filter or " or ".join( @@ -212,7 +211,7 @@ class TransparentProxy(object): self.addr_pid_map = dict() self.trusted_pids = set() self.tcptable2 = MIB_TCPTABLE2(0) - self.tcptable2_size = DWORD(0) + self.tcptable2_size = ctypes.wintypes.DWORD(0) self.request_local_handle = None self.request_local_thread = threading.Thread(target=self.request_local) self.request_local_thread.daemon = True @@ -244,23 +243,23 @@ class TransparentProxy(object): # real gateway if they are on the same network. self.icmp_handle = self.driver.open_handle( filter="icmp", - layer=Layer.NETWORK, - flags=Flag.DROP) + layer=enum.Layer.NETWORK, + flags=enum.Flag.DROP) self.response_handle = self.driver.open_handle( filter=self.response_filter, - layer=Layer.NETWORK) + layer=enum.Layer.NETWORK) self.response_thread.start() if self.mode == "forward" or self.mode == "both": self.request_forward_handle = self.driver.open_handle( filter=self.request_filter, - layer=Layer.NETWORK_FORWARD) + layer=enum.Layer.NETWORK_FORWARD) self.request_forward_thread.start() if self.mode == "local" or self.mode == "both": self.request_local_handle = self.driver.open_handle( filter=self.request_filter, - layer=Layer.NETWORK) + layer=enum.Layer.NETWORK) self.request_local_thread.start() def shutdown(self): @@ -288,9 +287,9 @@ class TransparentProxy(object): raise def fetch_pids(self): - ret = windll.iphlpapi.GetTcpTable2( - byref( - self.tcptable2), byref( + ret = ctypes.windll.iphlpapi.GetTcpTable2( + ctypes.byref( + self.tcptable2), ctypes.byref( self.tcptable2_size), 0) if ret == ERROR_INSUFFICIENT_BUFFER: self.tcptable2 = MIB_TCPTABLE2(self.tcptable2_size.value) @@ -352,7 +351,7 @@ class TransparentProxy(object): self.client_server_map[client] = server packet.dst_addr, packet.dst_port = self.proxy_addr, self.proxy_port - metadata.direction = Direction.INBOUND + metadata.direction = enum.Direction.INBOUND packet = self.driver.update_packet_checksums(packet) # Use any handle thats on the NETWORK layer - request_local may be diff --git a/mitmproxy/protocol/__init__.py b/mitmproxy/protocol/__init__.py index 3d9fa7d4..510cd195 100644 --- a/mitmproxy/protocol/__init__.py +++ b/mitmproxy/protocol/__init__.py @@ -25,15 +25,16 @@ Another subtle design goal of this architecture is that upstream connections sho as late as possible; this makes server replay without any outgoing connections possible. """ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division + from .base import Layer, ServerConnectionMixin -from .tls import TlsLayer -from .tls import is_tls_record_magic -from .tls import TlsClientHello from .http import UpstreamConnectLayer from .http1 import Http1Layer from .http2 import Http2Layer from .rawtcp import RawTCPLayer +from .tls import TlsClientHello +from .tls import TlsLayer +from .tls import is_tls_record_magic __all__ = [ "Layer", "ServerConnectionMixin", diff --git a/mitmproxy/protocol/base.py b/mitmproxy/protocol/base.py index c8e58d1b..11773385 100644 --- a/mitmproxy/protocol/base.py +++ b/mitmproxy/protocol/base.py @@ -1,11 +1,12 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division + import sys import six -from ..models import ServerConnection -from ..exceptions import ProtocolException -from netlib.exceptions import TcpException +import netlib.exceptions +from mitmproxy import exceptions +from mitmproxy import models class _LayerCodeCompletion(object): @@ -113,7 +114,7 @@ class ServerConnectionMixin(object): def __init__(self, server_address=None): super(ServerConnectionMixin, self).__init__() - self.server_conn = ServerConnection(server_address, (self.config.host, 0)) + self.server_conn = models.ServerConnection(server_address, (self.config.host, 0)) self.__check_self_connect() def __check_self_connect(self): @@ -128,7 +129,7 @@ class ServerConnectionMixin(object): address.host in ("localhost", "127.0.0.1", "::1") ) if self_connect: - raise ProtocolException( + raise exceptions.ProtocolException( "Invalid server address: {}\r\n" "The proxy shall not connect to itself.".format(repr(address)) ) @@ -154,7 +155,7 @@ class ServerConnectionMixin(object): self.server_conn.finish() self.server_conn.close() self.channel.tell("serverdisconnect", self.server_conn) - self.server_conn = ServerConnection(address, (source_address.host, 0)) + self.server_conn = models.ServerConnection(address, (source_address.host, 0)) def connect(self): """ @@ -165,15 +166,15 @@ class ServerConnectionMixin(object): ~mitmproxy.exceptions.ProtocolException: if the connection could not be established. """ if not self.server_conn.address: - raise ProtocolException("Cannot connect to server, no server address given.") + raise exceptions.ProtocolException("Cannot connect to server, no server address given.") self.log("serverconnect", "debug", [repr(self.server_conn.address)]) self.channel.ask("serverconnect", self.server_conn) try: self.server_conn.connect() - except TcpException as e: + except netlib.exceptions.TcpException as e: six.reraise( - ProtocolException, - ProtocolException( + exceptions.ProtocolException, + exceptions.ProtocolException( "Server connection to {} failed: {}".format( repr(self.server_conn.address), str(e) ) diff --git a/mitmproxy/protocol/http.py b/mitmproxy/protocol/http.py index d9111303..ae03ab7f 100644 --- a/mitmproxy/protocol/http.py +++ b/mitmproxy/protocol/http.py @@ -1,30 +1,21 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division import sys import traceback + +import h2.exceptions import six +import netlib.exceptions +from mitmproxy import exceptions +from mitmproxy import models +from mitmproxy import utils +from mitmproxy.protocol import base +from netlib import http from netlib import tcp -from netlib.exceptions import HttpException, HttpReadDisconnect, NetlibException -from netlib.http import Headers - -from h2.exceptions import H2Error - -from .. import utils -from ..exceptions import HttpProtocolException, Http2ProtocolException, ProtocolException -from ..models import ( - HTTPFlow, - HTTPResponse, - make_error_response, - make_connect_response, - Error, - expect_continue_response -) - -from .base import Layer -class _HttpTransmissionLayer(Layer): +class _HttpTransmissionLayer(base.Layer): def read_request(self): raise NotImplementedError() @@ -51,7 +42,7 @@ class _HttpTransmissionLayer(Layer): def send_response(self, response): if response.content is None: - raise HttpException("Cannot assemble flow with missing content") + raise netlib.exceptions.HttpException("Cannot assemble flow with missing content") self.send_response_headers(response) self.send_response_body(response, [response.content]) @@ -89,7 +80,7 @@ class ConnectServerConnection(object): __nonzero__ = __bool__ -class UpstreamConnectLayer(Layer): +class UpstreamConnectLayer(base.Layer): def __init__(self, ctx, connect_request): super(UpstreamConnectLayer, self).__init__(ctx) @@ -107,7 +98,7 @@ class UpstreamConnectLayer(Layer): self.send_request(self.connect_request) resp = self.read_response(self.connect_request) if resp.status_code != 200: - raise ProtocolException("Reconnect: Upstream server refuses CONNECT request") + raise exceptions.ProtocolException("Reconnect: Upstream server refuses CONNECT request") def connect(self): if not self.server_conn: @@ -129,7 +120,7 @@ class UpstreamConnectLayer(Layer): self.server_conn.address = address -class HttpLayer(Layer): +class HttpLayer(base.Layer): def __init__(self, ctx, mode): super(HttpLayer, self).__init__(ctx) @@ -166,16 +157,16 @@ class HttpLayer(Layer): self.handle_regular_mode_connect(request) return - except HttpReadDisconnect: + except netlib.exceptions.HttpReadDisconnect: # don't throw an error for disconnects that happen before/between requests. return - except NetlibException as e: + except netlib.exceptions.NetlibException as e: self.send_error_response(400, repr(e)) - six.reraise(ProtocolException, ProtocolException( + six.reraise(exceptions.ProtocolException, exceptions.ProtocolException( "Error in HTTP connection: %s" % repr(e)), sys.exc_info()[2]) try: - flow = HTTPFlow(self.client_conn, self.server_conn, live=self) + flow = models.HTTPFlow(self.client_conn, self.server_conn, live=self) flow.request = request # set upstream auth if self.mode == "upstream" and self.config.upstream_auth is not None: @@ -210,16 +201,16 @@ class HttpLayer(Layer): self.handle_upstream_mode_connect(flow.request.copy()) return - except (ProtocolException, NetlibException) as e: + except (exceptions.ProtocolException, netlib.exceptions.NetlibException) as e: self.send_error_response(502, repr(e)) if not flow.response: - flow.error = Error(str(e)) + flow.error = models.Error(str(e)) self.channel.ask("error", flow) self.log(traceback.format_exc(), "debug") return else: - six.reraise(ProtocolException, ProtocolException( + six.reraise(exceptions.ProtocolException, exceptions.ProtocolException( "Error in HTTP connection: %s" % repr(e)), sys.exc_info()[2]) finally: if flow: @@ -229,16 +220,16 @@ class HttpLayer(Layer): request = self.read_request() if request.headers.get("expect", "").lower() == "100-continue": # TODO: We may have to use send_response_headers for HTTP2 here. - self.send_response(expect_continue_response) + self.send_response(models.expect_continue_response) request.headers.pop("expect") request.body = b"".join(self.read_request_body(request)) return request def send_error_response(self, code, message): try: - response = make_error_response(code, message) + response = models.make_error_response(code, message) self.send_response(response) - except (NetlibException, H2Error, Http2ProtocolException): + except (netlib.exceptions.NetlibException, h2.exceptions.H2Error, exceptions.Http2ProtocolException): self.log(traceback.format_exc(), "debug") def change_upstream_proxy_server(self, address): @@ -249,7 +240,7 @@ class HttpLayer(Layer): def handle_regular_mode_connect(self, request): self.set_server((request.host, request.port)) - self.send_response(make_connect_response(request.data.http_version)) + self.send_response(models.make_connect_response(request.data.http_version)) layer = self.ctx.next_layer(self) layer() @@ -283,7 +274,7 @@ class HttpLayer(Layer): try: get_response() - except NetlibException as e: + except netlib.exceptions.NetlibException as e: self.log( "server communication error: %s" % repr(e), level="debug" @@ -300,9 +291,9 @@ class HttpLayer(Layer): # > read (100-n)% of large request # > send large request upstream - if isinstance(e, Http2ProtocolException): + if isinstance(e, exceptions.Http2ProtocolException): # do not try to reconnect for HTTP2 - raise ProtocolException("First and only attempt to get response via HTTP2 failed.") + raise exceptions.ProtocolException("First and only attempt to get response via HTTP2 failed.") self.disconnect() self.connect() @@ -345,7 +336,7 @@ class HttpLayer(Layer): flow.request.scheme = "https" if self.__initial_server_tls else "http" request_reply = self.channel.ask("request", flow) - if isinstance(request_reply, HTTPResponse): + if isinstance(request_reply, models.HTTPResponse): flow.response = request_reply return @@ -365,7 +356,7 @@ class HttpLayer(Layer): if not self.server_conn: self.connect() if tls: - raise HttpProtocolException("Cannot change scheme in upstream proxy mode.") + raise exceptions.HttpProtocolException("Cannot change scheme in upstream proxy mode.") """ # This is a very ugly (untested) workaround to solve a very ugly problem. if self.server_conn and self.server_conn.tls_established and not ssl: @@ -383,7 +374,7 @@ class HttpLayer(Layer): def validate_request(self, request): if request.first_line_format == "absolute" and request.scheme != "http": - raise HttpException("Invalid request scheme: %s" % request.scheme) + raise netlib.exceptions.HttpException("Invalid request scheme: %s" % request.scheme) expected_request_forms = { "regular": ("authority", "absolute",), @@ -396,7 +387,7 @@ class HttpLayer(Layer): err_message = "Invalid HTTP request form (expected: %s, got: %s)" % ( " or ".join(allowed_request_forms), request.first_line_format ) - raise HttpException(err_message) + raise netlib.exceptions.HttpException(err_message) if self.mode == "regular" and request.first_line_format == "absolute": request.first_line_format = "relative" @@ -406,10 +397,10 @@ class HttpLayer(Layer): if self.config.authenticator.authenticate(request.headers): self.config.authenticator.clean(request.headers) else: - self.send_response(make_error_response( + self.send_response(models.make_error_response( 407, "Proxy Authentication Required", - Headers(**self.config.authenticator.auth_challenge_headers()) + http.Headers(**self.config.authenticator.auth_challenge_headers()) )) return False return True diff --git a/mitmproxy/protocol/http1.py b/mitmproxy/protocol/http1.py index 940a4c98..7055a7fd 100644 --- a/mitmproxy/protocol/http1.py +++ b/mitmproxy/protocol/http1.py @@ -1,13 +1,11 @@ -from __future__ import (absolute_import, print_function, division) - +from __future__ import absolute_import, print_function, division +from mitmproxy import models +from mitmproxy.protocol import http from netlib.http import http1 -from .http import _HttpTransmissionLayer, HttpLayer -from ..models import HTTPRequest, HTTPResponse - -class Http1Layer(_HttpTransmissionLayer): +class Http1Layer(http._HttpTransmissionLayer): def __init__(self, ctx, mode): super(Http1Layer, self).__init__(ctx) @@ -15,7 +13,7 @@ class Http1Layer(_HttpTransmissionLayer): def read_request(self): req = http1.read_request(self.client_conn.rfile, body_size_limit=self.config.body_size_limit) - return HTTPRequest.wrap(req) + return models.HTTPRequest.wrap(req) def read_request_body(self, request): expected_size = http1.expected_http_body_size(request) @@ -27,7 +25,7 @@ class Http1Layer(_HttpTransmissionLayer): def read_response_headers(self): resp = http1.read_response_head(self.server_conn.rfile) - return HTTPResponse.wrap(resp) + return models.HTTPResponse.wrap(resp) def read_response_body(self, request, response): expected_size = http1.expected_http_body_size(request, response) @@ -63,5 +61,5 @@ class Http1Layer(_HttpTransmissionLayer): return close_connection def __call__(self): - layer = HttpLayer(self, self.mode) + layer = http.HttpLayer(self, self.mode) layer() diff --git a/mitmproxy/protocol/http2.py b/mitmproxy/protocol/http2.py index 30763c66..39512c8f 100644 --- a/mitmproxy/protocol/http2.py +++ b/mitmproxy/protocol/http2.py @@ -1,28 +1,27 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division import threading import time -from six.moves import queue - import traceback + +import h2.exceptions +import hyperframe import six -from h2.connection import H2Connection -from h2.exceptions import StreamClosedError +from h2 import connection from h2 import events -from hyperframe.frame import PriorityFrame - -from netlib.tcp import ssl_read_select -from netlib.exceptions import HttpException -from netlib.http import Headers -from netlib.utils import http2_read_raw_frame, parse_url +from six.moves import queue -from .base import Layer -from .http import _HttpTransmissionLayer, HttpLayer -from ..exceptions import ProtocolException, Http2ProtocolException -from ..models import HTTPRequest, HTTPResponse +import netlib.exceptions +from mitmproxy import exceptions +from mitmproxy import models +from mitmproxy.protocol import base +from mitmproxy.protocol import http +import netlib.http +from netlib import tcp +from netlib.http import http2 -class SafeH2Connection(H2Connection): +class SafeH2Connection(connection.H2Connection): def __init__(self, conn, *args, **kwargs): super(SafeH2Connection, self).__init__(*args, **kwargs) @@ -45,7 +44,7 @@ class SafeH2Connection(H2Connection): with self.lock: try: self.reset_stream(stream_id, error_code) - except StreamClosedError: # pragma: no cover + except h2.exceptions.StreamClosedError: # pragma: no cover # stream is already closed - good pass self.conn.send(self.data_to_send()) @@ -58,7 +57,7 @@ class SafeH2Connection(H2Connection): def safe_send_headers(self, is_zombie, stream_id, headers): with self.lock: if is_zombie(): # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") self.send_headers(stream_id, headers.fields) self.conn.send(self.data_to_send()) @@ -69,7 +68,7 @@ class SafeH2Connection(H2Connection): self.lock.acquire() if is_zombie(): # pragma: no cover self.lock.release() - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") max_outbound_frame_size = self.max_outbound_frame_size frame_chunk = chunk[position:position + max_outbound_frame_size] if self.local_flow_control_window(stream_id) < len(frame_chunk): @@ -82,12 +81,12 @@ class SafeH2Connection(H2Connection): position += max_outbound_frame_size with self.lock: if is_zombie(): # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") self.end_stream(stream_id) self.conn.send(self.data_to_send()) -class Http2Layer(Layer): +class Http2Layer(base.Layer): def __init__(self, ctx, mode): super(Http2Layer, self).__init__(ctx) @@ -107,13 +106,13 @@ class Http2Layer(Layer): self.active_conns.append(self.server_conn.connection) def connect(self): # pragma: no cover - raise Http2ProtocolException("HTTP2 layer should already have a connection.") + raise exceptions.Http2ProtocolException("HTTP2 layer should already have a connection.") def set_server(self): # pragma: no cover - raise Http2ProtocolException("Cannot change server for HTTP2 connections.") + raise exceptions.Http2ProtocolException("Cannot change server for HTTP2 connections.") def disconnect(self): # pragma: no cover - raise Http2ProtocolException("Cannot dis- or reconnect in HTTP2 connections.") + raise exceptions.Http2ProtocolException("Cannot dis- or reconnect in HTTP2 connections.") def next_layer(self): # pragma: no cover # WebSockets over HTTP/2? @@ -134,19 +133,19 @@ class Http2Layer(Layer): eid = event.stream_id if isinstance(event, events.RequestReceived): - headers = Headers([[k, v] for k, v in event.headers]) + headers = netlib.http.Headers([[k, v] for k, v in event.headers]) self.streams[eid] = Http2SingleStreamLayer(self, eid, headers) self.streams[eid].timestamp_start = time.time() self.streams[eid].start() elif isinstance(event, events.ResponseReceived): - headers = Headers([[k, v] for k, v in event.headers]) + headers = netlib.http.Headers([[k, v] for k, v in event.headers]) self.streams[eid].queued_data_length = 0 self.streams[eid].timestamp_start = time.time() self.streams[eid].response_headers = headers self.streams[eid].response_arrived.set() elif isinstance(event, events.DataReceived): if self.config.body_size_limit and self.streams[eid].queued_data_length > self.config.body_size_limit: - raise HttpException("HTTP body too large. Limit is {}.".format(self.config.body_size_limit)) + raise netlib.exceptions.HttpException("HTTP body too large. Limit is {}.".format(self.config.body_size_limit)) self.streams[eid].data_queue.put(event.data) self.streams[eid].queued_data_length += len(event.data) source_conn.h2.safe_increment_flow_control(event.stream_id, event.flow_controlled_length) @@ -177,7 +176,7 @@ class Http2Layer(Layer): self.client_conn.h2.push_stream(parent_eid, event.pushed_stream_id, event.headers) self.client_conn.send(self.client_conn.h2.data_to_send()) - headers = Headers([[str(k), str(v)] for k, v in event.headers]) + headers = netlib.http.Headers([[str(k), str(v)] for k, v in event.headers]) headers['x-mitmproxy-pushed'] = 'true' self.streams[event.pushed_stream_id] = Http2SingleStreamLayer(self, event.pushed_stream_id, headers) self.streams[event.pushed_stream_id].timestamp_start = time.time() @@ -196,7 +195,7 @@ class Http2Layer(Layer): depends_on = self.streams[depends_on].server_stream_id # weight is between 1 and 256 (inclusive), but represented as uint8 (0 to 255) - frame = PriorityFrame(stream_id, depends_on, event.weight - 1, event.exclusive) + frame = hyperframe.frame.PriorityFrame(stream_id, depends_on, event.weight - 1, event.exclusive) self.server_conn.send(frame.serialize()) elif isinstance(event, events.TrailersReceived): raise NotImplementedError() @@ -225,7 +224,7 @@ class Http2Layer(Layer): self.client_conn.send(self.client_conn.h2.data_to_send()) while True: - r = ssl_read_select(self.active_conns, 1) + r = tcp.ssl_read_select(self.active_conns, 1) for conn in r: source_conn = self.client_conn if conn == self.client_conn.connection else self.server_conn other_conn = self.server_conn if conn == self.client_conn.connection else self.client_conn @@ -233,7 +232,7 @@ class Http2Layer(Layer): with source_conn.h2.lock: try: - raw_frame = b''.join(http2_read_raw_frame(source_conn.rfile)) + raw_frame = b''.join(http2.framereader.http2_read_raw_frame(source_conn.rfile)) except: # read frame failed: connection closed self._kill_all_streams() @@ -251,7 +250,7 @@ class Http2Layer(Layer): self._cleanup_streams() -class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): +class Http2SingleStreamLayer(http._HttpTransmissionLayer, threading.Thread): def __init__(self, ctx, stream_id, request_headers): super(Http2SingleStreamLayer, self).__init__(ctx, name="Thread-Http2SingleStreamLayer-{}".format(stream_id)) @@ -306,6 +305,9 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): method = self.request_headers.get(':method', 'GET') scheme = self.request_headers.get(':scheme', 'https') path = self.request_headers.get(':path', '/') + self.request_headers.clear(":method") + self.request_headers.clear(":scheme") + self.request_headers.clear(":path") host = None port = None @@ -316,7 +318,7 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): else: # pragma: no cover first_line_format = "absolute" # FIXME: verify if path or :host contains what we need - scheme, host, port, _ = parse_url(path) + scheme, host, port, _ = netlib.http.url.parse(path) if authority: host, _, port = authority.partition(':') @@ -332,7 +334,7 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): data.append(self.request_data_queue.get()) data = b"".join(data) - return HTTPRequest( + return models.HTTPRequest( first_line_format, method, scheme, @@ -357,15 +359,20 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): with self.server_conn.h2.lock: # We must not assign a stream id if we are already a zombie. if self.zombie: # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") self.server_stream_id = self.server_conn.h2.get_next_available_stream_id() self.server_to_client_stream_ids[self.server_stream_id] = self.client_stream_id + headers = message.headers.copy() + headers.insert(0, ":path", message.path) + headers.insert(0, ":method", message.method) + headers.insert(0, ":scheme", message.scheme) + self.server_conn.h2.safe_send_headers( self.is_zombie, self.server_stream_id, - message.headers + headers ) self.server_conn.h2.safe_send_body( self.is_zombie, @@ -373,18 +380,20 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): message.body ) if self.zombie: # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") def read_response_headers(self): self.response_arrived.wait() status_code = int(self.response_headers.get(':status', 502)) + headers = self.response_headers.copy() + headers.clear(":status") - return HTTPResponse( + return models.HTTPResponse( http_version=b"HTTP/2.0", status_code=status_code, reason='', - headers=self.response_headers, + headers=headers, content=None, timestamp_start=self.timestamp_start, timestamp_end=self.timestamp_end, @@ -401,16 +410,18 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): yield self.response_data_queue.get() return if self.zombie: # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") def send_response_headers(self, response): + headers = response.headers.copy() + headers.insert(0, ":status", str(response.status_code)) self.client_conn.h2.safe_send_headers( self.is_zombie, self.client_stream_id, - response.headers + headers ) if self.zombie: # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") def send_response_body(self, _response, chunks): self.client_conn.h2.safe_send_body( @@ -419,7 +430,7 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): chunks ) if self.zombie: # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") def check_close_connection(self, flow): # This layer only handles a single stream. @@ -434,11 +445,11 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): self() def __call__(self): - layer = HttpLayer(self, self.mode) + layer = http.HttpLayer(self, self.mode) try: layer() - except ProtocolException as e: + except exceptions.ProtocolException as e: self.log(repr(e), "info") self.log(traceback.format_exc(), "debug") diff --git a/mitmproxy/protocol/http_replay.py b/mitmproxy/protocol/http_replay.py index e78af074..5928c0af 100644 --- a/mitmproxy/protocol/http_replay.py +++ b/mitmproxy/protocol/http_replay.py @@ -1,13 +1,14 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division + import threading import traceback -from mitmproxy.exceptions import ReplayException -from netlib.exceptions import HttpException, TcpException + +import netlib.exceptions +from mitmproxy import controller +from mitmproxy import exceptions +from mitmproxy import models from netlib.http import http1 -from ..controller import Channel -from ..models import Error, HTTPResponse, ServerConnection, make_connect_request -from ..exceptions import Kill # TODO: Doesn't really belong into mitmproxy.protocol... @@ -22,7 +23,7 @@ class RequestReplayThread(threading.Thread): """ self.config, self.flow = config, flow if event_queue: - self.channel = Channel(event_queue, should_exit) + self.channel = controller.Channel(event_queue, should_exit) else: self.channel = None super(RequestReplayThread, self).__init__() @@ -36,17 +37,17 @@ class RequestReplayThread(threading.Thread): # If we have a channel, run script hooks. if self.channel: request_reply = self.channel.ask("request", self.flow) - if isinstance(request_reply, HTTPResponse): + if isinstance(request_reply, models.HTTPResponse): self.flow.response = request_reply if not self.flow.response: # In all modes, we directly connect to the server displayed if self.config.mode == "upstream": server_address = self.config.upstream_server.address - server = ServerConnection(server_address, (self.config.host, 0)) + server = models.ServerConnection(server_address, (self.config.host, 0)) server.connect() if r.scheme == "https": - connect_request = make_connect_request((r.host, r.port)) + connect_request = models.make_connect_request((r.host, r.port)) server.wfile.write(http1.assemble_request(connect_request)) server.wfile.flush() resp = http1.read_response( @@ -55,17 +56,17 @@ class RequestReplayThread(threading.Thread): body_size_limit=self.config.body_size_limit ) if resp.status_code != 200: - raise ReplayException("Upstream server refuses CONNECT request") + raise exceptions.ReplayException("Upstream server refuses CONNECT request") server.establish_ssl( self.config.clientcerts, sni=self.flow.server_conn.sni ) r.first_line_format = "relative" else: - r.first_line_format= "absolute" + r.first_line_format = "absolute" else: server_address = (r.host, r.port) - server = ServerConnection(server_address, (self.config.host, 0)) + server = models.ServerConnection(server_address, (self.config.host, 0)) server.connect() if r.scheme == "https": server.establish_ssl( @@ -77,20 +78,20 @@ class RequestReplayThread(threading.Thread): server.wfile.write(http1.assemble_request(r)) server.wfile.flush() self.flow.server_conn = server - self.flow.response = HTTPResponse.wrap(http1.read_response( + self.flow.response = models.HTTPResponse.wrap(http1.read_response( server.rfile, r, body_size_limit=self.config.body_size_limit )) if self.channel: response_reply = self.channel.ask("response", self.flow) - if response_reply == Kill: - raise Kill() - except (ReplayException, HttpException, TcpException) as e: - self.flow.error = Error(str(e)) + if response_reply == exceptions.Kill: + raise exceptions.Kill() + except (exceptions.ReplayException, netlib.exceptions.NetlibException) as e: + self.flow.error = models.Error(str(e)) if self.channel: self.channel.ask("error", self.flow) - except Kill: + except exceptions.Kill: # Kill should only be raised if there's a channel in the # first place. from ..proxy.root_context import Log diff --git a/mitmproxy/protocol/rawtcp.py b/mitmproxy/protocol/rawtcp.py index 1b546c40..70486cc4 100644 --- a/mitmproxy/protocol/rawtcp.py +++ b/mitmproxy/protocol/rawtcp.py @@ -1,21 +1,17 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division + import socket -import six -import sys from OpenSSL import SSL -from netlib.exceptions import TcpException - -from netlib.tcp import ssl_read_select -from netlib.utils import clean_bin -from ..exceptions import ProtocolException -from ..models import Error -from ..models.tcp import TCPFlow, TCPMessage -from .base import Layer +import netlib.exceptions +import netlib.tcp +from mitmproxy import models +from mitmproxy.models import tcp +from mitmproxy.protocol import base -class RawTCPLayer(Layer): +class RawTCPLayer(base.Layer): chunk_size = 4096 def __init__(self, ctx, ignore=False): @@ -26,7 +22,7 @@ class RawTCPLayer(Layer): self.connect() if not self.ignore: - flow = TCPFlow(self.client_conn, self.server_conn, self) + flow = models.TCPFlow(self.client_conn, self.server_conn, self) self.channel.ask("tcp_open", flow) buf = memoryview(bytearray(self.chunk_size)) @@ -37,7 +33,7 @@ class RawTCPLayer(Layer): try: while not self.channel.should_exit.is_set(): - r = ssl_read_select(conns, 10) + r = netlib.tcp.ssl_read_select(conns, 10) for conn in r: dst = server if conn == client else client @@ -56,15 +52,15 @@ class RawTCPLayer(Layer): return continue - tcp_message = TCPMessage(dst == server, buf[:size].tobytes()) + tcp_message = tcp.TCPMessage(dst == server, buf[:size].tobytes()) if not self.ignore: flow.messages.append(tcp_message) self.channel.ask("tcp_message", flow) dst.sendall(tcp_message.content) - except (socket.error, TcpException, SSL.Error) as e: + except (socket.error, netlib.exceptions.TcpException, SSL.Error) as e: if not self.ignore: - flow.error = Error("TCP connection closed unexpectedly: {}".format(repr(e))) + flow.error = models.Error("TCP connection closed unexpectedly: {}".format(repr(e))) self.channel.tell("tcp_error", flow) finally: if not self.ignore: diff --git a/mitmproxy/protocol/tls.py b/mitmproxy/protocol/tls.py index 74c55ab4..9f883b2b 100644 --- a/mitmproxy/protocol/tls.py +++ b/mitmproxy/protocol/tls.py @@ -1,16 +1,15 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division import struct import sys -from construct import ConstructError +import construct import six -from netlib.exceptions import InvalidCertificateException -from netlib.exceptions import TlsException -from ..contrib.tls._constructs import ClientHello -from ..exceptions import ProtocolException, TlsProtocolException, ClientHandshakeException -from .base import Layer +import netlib.exceptions +from mitmproxy import exceptions +from mitmproxy.contrib.tls import _constructs +from mitmproxy.protocol import base # taken from https://testssl.sh/openssl-rfc.mappping.html @@ -246,11 +245,11 @@ def get_client_hello(client_conn): while len(client_hello) < client_hello_size: record_header = client_conn.rfile.peek(offset + 5)[offset:] if not is_tls_record_magic(record_header) or len(record_header) != 5: - raise TlsProtocolException('Expected TLS record, got "%s" instead.' % record_header) + raise exceptions.TlsProtocolException('Expected TLS record, got "%s" instead.' % record_header) record_size = struct.unpack("!H", record_header[3:])[0] + 5 record_body = client_conn.rfile.peek(offset + record_size)[offset + 5:] if len(record_body) != record_size - 5: - raise TlsProtocolException("Unexpected EOF in TLS handshake: %s" % record_body) + raise exceptions.TlsProtocolException("Unexpected EOF in TLS handshake: %s" % record_body) client_hello += record_body offset += record_size client_hello_size = struct.unpack("!I", b'\x00' + client_hello[1:4])[0] + 4 @@ -260,7 +259,7 @@ def get_client_hello(client_conn): class TlsClientHello(object): def __init__(self, raw_client_hello): - self._client_hello = ClientHello.parse(raw_client_hello) + self._client_hello = _constructs.ClientHello.parse(raw_client_hello) def raw(self): return self._client_hello @@ -273,9 +272,9 @@ class TlsClientHello(object): def sni(self): for extension in self._client_hello.extensions: is_valid_sni_extension = ( - extension.type == 0x00 - and len(extension.server_names) == 1 - and extension.server_names[0].type == 0 + extension.type == 0x00 and + len(extension.server_names) == 1 and + extension.server_names[0].type == 0 ) if is_valid_sni_extension: return extension.server_names[0].name @@ -297,21 +296,24 @@ class TlsClientHello(object): """ try: raw_client_hello = get_client_hello(client_conn)[4:] # exclude handshake header. - except ProtocolException as e: - raise TlsProtocolException('Cannot read raw Client Hello: %s' % repr(e)) + except exceptions.ProtocolException as e: + raise exceptions.TlsProtocolException('Cannot read raw Client Hello: %s' % repr(e)) try: return cls(raw_client_hello) - except ConstructError as e: - raise TlsProtocolException('Cannot parse Client Hello: %s, Raw Client Hello: %s' % - (repr(e), raw_client_hello.encode("hex"))) + except construct.ConstructError as e: + raise exceptions.TlsProtocolException( + 'Cannot parse Client Hello: %s, Raw Client Hello: %s' % + (repr(e), raw_client_hello.encode("hex")) + ) def __repr__(self): return "TlsClientHello( sni: %s alpn_protocols: %s, cipher_suites: %s)" % \ (self.sni, self.alpn_protocols, self.cipher_suites) -class TlsLayer(Layer): +class TlsLayer(base.Layer): + """ The TLS layer implements transparent TLS connections. @@ -344,7 +346,7 @@ class TlsLayer(Layer): # Peek into the connection, read the initial client hello and parse it to obtain SNI and ALPN values. try: self._client_hello = TlsClientHello.from_client_conn(self.client_conn) - except TlsProtocolException as e: + except exceptions.TlsProtocolException as e: self.log("Cannot parse Client Hello: %s" % repr(e), "error") # Do we need to do a server handshake now? @@ -361,17 +363,17 @@ class TlsLayer(Layer): # what is supported by the server # 2.5 The client did not sent a SNI value, we don't know the certificate subject. client_tls_requires_server_connection = ( - self._server_tls - and not self.config.no_upstream_cert - and ( - self.config.add_upstream_certs_to_client_chain - or self._client_hello.alpn_protocols - or not self._client_hello.sni + self._server_tls and + not self.config.no_upstream_cert and + ( + self.config.add_upstream_certs_to_client_chain or + self._client_hello.alpn_protocols or + not self._client_hello.sni ) ) establish_server_tls_now = ( - (self.server_conn and self._server_tls) - or client_tls_requires_server_connection + (self.server_conn and self._server_tls) or + client_tls_requires_server_connection ) if self._client_tls and establish_server_tls_now: @@ -469,9 +471,9 @@ class TlsLayer(Layer): cert, key, chain_file = self._find_cert() if self.config.add_upstream_certs_to_client_chain: - extra_certs = self.server_conn.server_certs + extra_certs = self.server_conn.server_certs else: - extra_certs = None + extra_certs = None try: self.client_conn.convert_to_ssl( @@ -482,17 +484,17 @@ class TlsLayer(Layer): dhparams=self.config.certstore.dhparams, chain_file=chain_file, alpn_select_callback=self.__alpn_select_callback, - extra_chain_certs = extra_certs, + extra_chain_certs=extra_certs, ) # Some TLS clients will not fail the handshake, # but will immediately throw an "unexpected eof" error on the first read. # The reason for this might be difficult to find, so we try to peek here to see if it # raises ann error. self.client_conn.rfile.peek(1) - except TlsException as e: + except netlib.exceptions.TlsException as e: six.reraise( - ClientHandshakeException, - ClientHandshakeException( + exceptions.ClientHandshakeException, + exceptions.ClientHandshakeException( "Cannot establish TLS with client (sni: {sni}): {e}".format( sni=self._client_hello.sni, e=repr(e) ), @@ -507,7 +509,9 @@ class TlsLayer(Layer): # We only support http/1.1 and h2. # If the server only supports spdy (next to http/1.1), it may select that # and mitmproxy would enter TCP passthrough mode, which we want to avoid. - deprecated_http2_variant = lambda x: x.startswith(b"h2-") or x.startswith(b"spdy") + def deprecated_http2_variant(x): + return x.startswith(b"h2-") or x.startswith(b"spdy") + if self._client_hello.alpn_protocols: alpn = [x for x in self._client_hello.alpn_protocols if not deprecated_http2_variant(x)] else: @@ -541,7 +545,7 @@ class TlsLayer(Layer): (tls_cert_err['depth'], tls_cert_err['errno']), "error") self.log("Ignoring server verification error, continuing with connection", "error") - except InvalidCertificateException as e: + except netlib.exceptions.InvalidCertificateException as e: tls_cert_err = self.server_conn.ssl_verification_error self.log( "TLS verification failed for upstream server at depth %s with error: %s" % @@ -549,18 +553,18 @@ class TlsLayer(Layer): "error") self.log("Aborting connection attempt", "error") six.reraise( - TlsProtocolException, - TlsProtocolException("Cannot establish TLS with {address} (sni: {sni}): {e}".format( + exceptions.TlsProtocolException, + exceptions.TlsProtocolException("Cannot establish TLS with {address} (sni: {sni}): {e}".format( address=repr(self.server_conn.address), sni=self.server_sni, e=repr(e), )), sys.exc_info()[2] ) - except TlsException as e: + except netlib.exceptions.TlsException as e: six.reraise( - TlsProtocolException, - TlsProtocolException("Cannot establish TLS with {address} (sni: {sni}): {e}".format( + exceptions.TlsProtocolException, + exceptions.TlsProtocolException("Cannot establish TLS with {address} (sni: {sni}): {e}".format( address=repr(self.server_conn.address), sni=self.server_sni, e=repr(e), diff --git a/mitmproxy/proxy/__init__.py b/mitmproxy/proxy/__init__.py index be7f5207..ada9fa12 100644 --- a/mitmproxy/proxy/__init__.py +++ b/mitmproxy/proxy/__init__.py @@ -1,8 +1,8 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division -from .server import ProxyServer, DummyServer from .config import ProxyConfig from .root_context import RootContext, Log +from .server import ProxyServer, DummyServer __all__ = [ "ProxyServer", "DummyServer", diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py index 58b568ea..9246fe04 100644 --- a/mitmproxy/proxy/config.py +++ b/mitmproxy/proxy/config.py @@ -1,4 +1,5 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division + import collections import os import re @@ -6,18 +7,26 @@ import re import six from OpenSSL import SSL -from netlib import certutils, tcp +from mitmproxy import platform +from netlib import certutils +from netlib import human +from netlib import tcp from netlib.http import authentication -from netlib.tcp import Address, sslversion_choices - -from .. import utils, platform CONF_BASENAME = "mitmproxy" CA_DIR = "~/.mitmproxy" # We manually need to specify this, otherwise OpenSSL may select a non-HTTP2 cipher by default. # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=apache-2.2.15&openssl=1.0.2&hsts=yes&profile=old -DEFAULT_CLIENT_CIPHERS = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" +DEFAULT_CLIENT_CIPHERS = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:" \ + "ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:" \ + "ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:" \ + "ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:" \ + "DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:" \ + "DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:" \ + "AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:" \ + "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:" \ + "!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" class HostMatcher(object): @@ -58,7 +67,7 @@ class ProxyConfig: body_size_limit=None, mode="regular", upstream_server=None, - upstream_auth = None, + upstream_auth=None, authenticator=None, ignore_hosts=tuple(), tcp_hosts=tuple(), @@ -83,7 +92,7 @@ class ProxyConfig: self.body_size_limit = body_size_limit self.mode = mode if upstream_server: - self.upstream_server = ServerSpec(upstream_server[0], Address.wrap(upstream_server[1])) + self.upstream_server = ServerSpec(upstream_server[0], tcp.Address.wrap(upstream_server[1])) self.upstream_auth = upstream_auth else: self.upstream_server = None @@ -103,9 +112,9 @@ class ProxyConfig: self.certstore.add_cert_file(spec, cert) self.openssl_method_client, self.openssl_options_client = \ - sslversion_choices[ssl_version_client] + tcp.sslversion_choices[ssl_version_client] self.openssl_method_server, self.openssl_options_server = \ - sslversion_choices[ssl_version_server] + tcp.sslversion_choices[ssl_version_server] if ssl_verify_upstream_cert: self.openssl_verification_mode_server = SSL.VERIFY_PEER @@ -117,10 +126,12 @@ class ProxyConfig: def process_proxy_options(parser, options): - body_size_limit = utils.parse_size(options.body_size_limit) + body_size_limit = options.body_size_limit + if body_size_limit: + body_size_limit = human.parse_size(body_size_limit) c = 0 - mode, upstream_server, upstream_auth = "regular", None, None + mode, upstream_server, upstream_auth = "regular", None, None if options.transparent_proxy: c += 1 if not platform.resolver: @@ -161,7 +172,7 @@ def process_proxy_options(parser, options): options.clientcerts = os.path.expanduser(options.clientcerts) if not os.path.exists(options.clientcerts): return parser.error( - "Client certificate path does not exist: %s" % options.clientcerts + "Client certificate path does not exist: %s" % options.clientcerts ) if options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd: diff --git a/mitmproxy/proxy/modes/__init__.py b/mitmproxy/proxy/modes/__init__.py index f014ed98..fa62570c 100644 --- a/mitmproxy/proxy/modes/__init__.py +++ b/mitmproxy/proxy/modes/__init__.py @@ -1,4 +1,5 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division + from .http_proxy import HttpProxy, HttpUpstreamProxy from .reverse_proxy import ReverseProxy from .socks_proxy import Socks5Proxy diff --git a/mitmproxy/proxy/modes/http_proxy.py b/mitmproxy/proxy/modes/http_proxy.py index e19062b9..bc64ccd2 100644 --- a/mitmproxy/proxy/modes/http_proxy.py +++ b/mitmproxy/proxy/modes/http_proxy.py @@ -1,9 +1,9 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division -from ...protocol import Layer, ServerConnectionMixin +from mitmproxy import protocol -class HttpProxy(Layer, ServerConnectionMixin): +class HttpProxy(protocol.Layer, protocol.ServerConnectionMixin): def __call__(self): layer = self.ctx.next_layer(self) @@ -14,7 +14,7 @@ class HttpProxy(Layer, ServerConnectionMixin): self.disconnect() -class HttpUpstreamProxy(Layer, ServerConnectionMixin): +class HttpUpstreamProxy(protocol.Layer, protocol.ServerConnectionMixin): def __init__(self, ctx, server_address): super(HttpUpstreamProxy, self).__init__(ctx, server_address=server_address) diff --git a/mitmproxy/proxy/modes/reverse_proxy.py b/mitmproxy/proxy/modes/reverse_proxy.py index c8e80a10..3739ac0e 100644 --- a/mitmproxy/proxy/modes/reverse_proxy.py +++ b/mitmproxy/proxy/modes/reverse_proxy.py @@ -1,9 +1,9 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division -from ...protocol import Layer, ServerConnectionMixin +from mitmproxy import protocol -class ReverseProxy(Layer, ServerConnectionMixin): +class ReverseProxy(protocol.Layer, protocol.ServerConnectionMixin): def __init__(self, ctx, server_address, server_tls): super(ReverseProxy, self).__init__(ctx, server_address=server_address) diff --git a/mitmproxy/proxy/modes/socks_proxy.py b/mitmproxy/proxy/modes/socks_proxy.py index e2ce44ae..19437835 100644 --- a/mitmproxy/proxy/modes/socks_proxy.py +++ b/mitmproxy/proxy/modes/socks_proxy.py @@ -1,13 +1,13 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division -from netlib import socks, tcp -from netlib.exceptions import TcpException +import netlib.exceptions +from mitmproxy import exceptions +from mitmproxy import protocol +from netlib import socks +from netlib import tcp -from ...exceptions import Socks5ProtocolException -from ...protocol import Layer, ServerConnectionMixin - -class Socks5Proxy(Layer, ServerConnectionMixin): +class Socks5Proxy(protocol.Layer, protocol.ServerConnectionMixin): def __init__(self, ctx): super(Socks5Proxy, self).__init__(ctx) @@ -51,8 +51,8 @@ class Socks5Proxy(Layer, ServerConnectionMixin): connect_reply.to_file(self.client_conn.wfile) self.client_conn.wfile.flush() - except (socks.SocksError, TcpException) as e: - raise Socks5ProtocolException("SOCKS5 mode failure: %s" % repr(e)) + except (socks.SocksError, netlib.exceptions.TcpException) as e: + raise exceptions.Socks5ProtocolException("SOCKS5 mode failure: %s" % repr(e)) # https://github.com/mitmproxy/mitmproxy/issues/839 address_bytes = (connect_request.addr.host.encode("idna"), connect_request.addr.port) diff --git a/mitmproxy/proxy/modes/transparent_proxy.py b/mitmproxy/proxy/modes/transparent_proxy.py index 3fdda656..c7df7900 100644 --- a/mitmproxy/proxy/modes/transparent_proxy.py +++ b/mitmproxy/proxy/modes/transparent_proxy.py @@ -1,11 +1,11 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division -from ... import platform -from ...exceptions import ProtocolException -from ...protocol import Layer, ServerConnectionMixin +from mitmproxy import exceptions +from mitmproxy import platform +from mitmproxy import protocol -class TransparentProxy(Layer, ServerConnectionMixin): +class TransparentProxy(protocol.Layer, protocol.ServerConnectionMixin): def __init__(self, ctx): super(TransparentProxy, self).__init__(ctx) @@ -15,7 +15,7 @@ class TransparentProxy(Layer, ServerConnectionMixin): try: self.server_conn.address = self.resolver.original_addr(self.client_conn.connection) except Exception as e: - raise ProtocolException("Transparent mode failure: %s" % repr(e)) + raise exceptions.ProtocolException("Transparent mode failure: %s" % repr(e)) layer = self.ctx.next_layer(self) try: diff --git a/mitmproxy/proxy/root_context.py b/mitmproxy/proxy/root_context.py index 96e7aab6..57183c7e 100644 --- a/mitmproxy/proxy/root_context.py +++ b/mitmproxy/proxy/root_context.py @@ -1,15 +1,13 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division + import sys import six -from mitmproxy.exceptions import ProtocolException, TlsProtocolException -from netlib.exceptions import TcpException -from ..protocol import ( - RawTCPLayer, TlsLayer, Http1Layer, Http2Layer, is_tls_record_magic, ServerConnectionMixin, - UpstreamConnectLayer, TlsClientHello -) -from .modes import HttpProxy, HttpUpstreamProxy, ReverseProxy +import netlib.exceptions +from mitmproxy import exceptions +from mitmproxy import protocol +from mitmproxy.proxy import modes class RootContext(object): @@ -50,53 +48,53 @@ class RootContext(object): def _next_layer(self, top_layer): try: d = top_layer.client_conn.rfile.peek(3) - except TcpException as e: - six.reraise(ProtocolException, ProtocolException(str(e)), sys.exc_info()[2]) - client_tls = is_tls_record_magic(d) + except netlib.exceptions.TcpException as e: + six.reraise(exceptions.ProtocolException, exceptions.ProtocolException(str(e)), sys.exc_info()[2]) + client_tls = protocol.is_tls_record_magic(d) # 1. check for --ignore if self.config.check_ignore: ignore = self.config.check_ignore(top_layer.server_conn.address) if not ignore and client_tls: try: - client_hello = TlsClientHello.from_client_conn(self.client_conn) - except TlsProtocolException as e: + client_hello = protocol.TlsClientHello.from_client_conn(self.client_conn) + except exceptions.TlsProtocolException as e: self.log("Cannot parse Client Hello: %s" % repr(e), "error") else: ignore = self.config.check_ignore((client_hello.sni, 443)) if ignore: - return RawTCPLayer(top_layer, ignore=True) + return protocol.RawTCPLayer(top_layer, ignore=True) # 2. Always insert a TLS layer, even if there's neither client nor server tls. # An inline script may upgrade from http to https, # in which case we need some form of TLS layer. - if isinstance(top_layer, ReverseProxy): - return TlsLayer(top_layer, client_tls, top_layer.server_tls) - if isinstance(top_layer, ServerConnectionMixin) or isinstance(top_layer, UpstreamConnectLayer): - return TlsLayer(top_layer, client_tls, client_tls) + if isinstance(top_layer, modes.ReverseProxy): + return protocol.TlsLayer(top_layer, client_tls, top_layer.server_tls) + if isinstance(top_layer, protocol.ServerConnectionMixin) or isinstance(top_layer, protocol.UpstreamConnectLayer): + return protocol.TlsLayer(top_layer, client_tls, client_tls) # 3. In Http Proxy mode and Upstream Proxy mode, the next layer is fixed. - if isinstance(top_layer, TlsLayer): - if isinstance(top_layer.ctx, HttpProxy): - return Http1Layer(top_layer, "regular") - if isinstance(top_layer.ctx, HttpUpstreamProxy): - return Http1Layer(top_layer, "upstream") + if isinstance(top_layer, protocol.TlsLayer): + if isinstance(top_layer.ctx, modes.HttpProxy): + return protocol.Http1Layer(top_layer, "regular") + if isinstance(top_layer.ctx, modes.HttpUpstreamProxy): + return protocol.Http1Layer(top_layer, "upstream") # 4. Check for other TLS cases (e.g. after CONNECT). if client_tls: - return TlsLayer(top_layer, True, True) + return protocol.TlsLayer(top_layer, True, True) # 4. Check for --tcp if self.config.check_tcp(top_layer.server_conn.address): - return RawTCPLayer(top_layer) + return protocol.RawTCPLayer(top_layer) # 5. Check for TLS ALPN (HTTP1/HTTP2) - if isinstance(top_layer, TlsLayer): + if isinstance(top_layer, protocol.TlsLayer): alpn = top_layer.client_conn.get_alpn_proto_negotiated() if alpn == b'h2': - return Http2Layer(top_layer, 'transparent') + return protocol.Http2Layer(top_layer, 'transparent') if alpn == b'http/1.1': - return Http1Layer(top_layer, 'transparent') + return protocol.Http1Layer(top_layer, 'transparent') # 6. Check for raw tcp mode is_ascii = ( @@ -105,10 +103,10 @@ class RootContext(object): all(65 <= x <= 90 and 97 <= x <= 122 for x in six.iterbytes(d)) ) if self.config.rawtcp and not is_ascii: - return RawTCPLayer(top_layer) + return protocol.RawTCPLayer(top_layer) # 7. Assume HTTP1 by default - return Http1Layer(top_layer, 'transparent') + return protocol.Http1Layer(top_layer, 'transparent') def log(self, msg, level, subs=()): """ @@ -132,7 +130,6 @@ class RootContext(object): class Log(object): - def __init__(self, msg, level="info"): self.msg = msg self.level = level diff --git a/mitmproxy/proxy/server.py b/mitmproxy/proxy/server.py index 8483d3df..7e96911a 100644 --- a/mitmproxy/proxy/server.py +++ b/mitmproxy/proxy/server.py @@ -1,17 +1,18 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division -import traceback -import sys import socket +import sys +import traceback + import six +import netlib.exceptions +from mitmproxy import exceptions +from mitmproxy import models +from mitmproxy.proxy import modes +from mitmproxy.proxy import root_context from netlib import tcp -from netlib.exceptions import TcpException -from netlib.http.http1 import assemble_response -from ..exceptions import ProtocolException, ServerException, ClientHandshakeException, Kill -from ..models import ClientConnection, make_error_response -from .modes import HttpUpstreamProxy, HttpProxy, ReverseProxy, TransparentProxy, Socks5Proxy -from .root_context import RootContext, Log +from netlib.http import http1 class DummyServer: @@ -43,8 +44,8 @@ class ProxyServer(tcp.TCPServer): super(ProxyServer, self).__init__((config.host, config.port)) except socket.error as e: six.reraise( - ServerException, - ServerException('Error starting proxy server: ' + repr(e)), + exceptions.ServerException, + exceptions.ServerException('Error starting proxy server: ' + repr(e)), sys.exc_info()[2] ) self.channel = None @@ -67,7 +68,7 @@ class ConnectionHandler(object): def __init__(self, client_conn, client_address, config, channel): self.config = config """@type: mitmproxy.proxy.config.ProxyConfig""" - self.client_conn = ClientConnection( + self.client_conn = models.ClientConnection( client_conn, client_address, None) @@ -76,7 +77,7 @@ class ConnectionHandler(object): """@type: mitmproxy.controller.Channel""" def _create_root_layer(self): - root_context = RootContext( + root_ctx = root_context.RootContext( self.client_conn, self.config, self.channel @@ -84,25 +85,25 @@ class ConnectionHandler(object): mode = self.config.mode if mode == "upstream": - return HttpUpstreamProxy( - root_context, + return modes.HttpUpstreamProxy( + root_ctx, self.config.upstream_server.address ) elif mode == "transparent": - return TransparentProxy(root_context) + return modes.TransparentProxy(root_ctx) elif mode == "reverse": server_tls = self.config.upstream_server.scheme == "https" - return ReverseProxy( - root_context, + return modes.ReverseProxy( + root_ctx, self.config.upstream_server.address, server_tls ) elif mode == "socks5": - return Socks5Proxy(root_context) + return modes.Socks5Proxy(root_ctx) elif mode == "regular": - return HttpProxy(root_context) + return modes.HttpProxy(root_ctx) elif callable(mode): # pragma: no cover - return mode(root_context) + return mode(root_ctx) else: # pragma: no cover raise ValueError("Unknown proxy mode: %s" % mode) @@ -114,11 +115,11 @@ class ConnectionHandler(object): try: root_layer = self.channel.ask("clientconnect", root_layer) root_layer() - except Kill: + except exceptions.Kill: self.log("Connection killed", "info") - except ProtocolException as e: + except exceptions.ProtocolException as e: - if isinstance(e, ClientHandshakeException): + if isinstance(e, exceptions.ClientHandshakeException): self.log( "Client Handshake failed. " "The client may not trust the proxy's certificate for {}.".format(e.server), @@ -133,9 +134,9 @@ class ConnectionHandler(object): # we send an HTTP error response, which is both # understandable by HTTP clients and humans. try: - error_response = make_error_response(502, repr(e)) - self.client_conn.send(assemble_response(error_response)) - except TcpException: + error_response = models.make_error_response(502, repr(e)) + self.client_conn.send(http1.assemble_response(error_response)) + except netlib.exceptions.TcpException: pass except Exception: self.log(traceback.format_exc(), "error") @@ -149,4 +150,4 @@ class ConnectionHandler(object): def log(self, msg, level): msg = "{}: {}".format(repr(self.client_conn.address), msg) - self.channel.tell("log", Log(msg, level)) + self.channel.tell("log", root_context.Log(msg, level)) diff --git a/mitmproxy/script/__init__.py b/mitmproxy/script/__init__.py index 3ee19b04..d6bff4c7 100644 --- a/mitmproxy/script/__init__.py +++ b/mitmproxy/script/__init__.py @@ -1,8 +1,8 @@ +from . import reloader +from .concurrent import concurrent from .script import Script from .script_context import ScriptContext -from .concurrent import concurrent from ..exceptions import ScriptException -from . import reloader __all__ = [ "Script", diff --git a/mitmproxy/script/concurrent.py b/mitmproxy/script/concurrent.py index 2f25e78c..43d0d328 100644 --- a/mitmproxy/script/concurrent.py +++ b/mitmproxy/script/concurrent.py @@ -3,6 +3,7 @@ This module provides a @concurrent decorator primitive to offload computations from mitmproxy's main master thread. """ from __future__ import absolute_import, print_function, division + import threading diff --git a/mitmproxy/script/reloader.py b/mitmproxy/script/reloader.py index 99ce7f60..f5470bbf 100644 --- a/mitmproxy/script/reloader.py +++ b/mitmproxy/script/reloader.py @@ -1,6 +1,10 @@ +from __future__ import absolute_import, print_function, division + import os import sys + from watchdog.events import RegexMatchingEventHandler + if sys.platform == 'darwin': # pragma: no cover from watchdog.observers.polling import PollingObserver as Observer else: diff --git a/mitmproxy/script/script.py b/mitmproxy/script/script.py index 484025b4..70f74817 100644 --- a/mitmproxy/script/script.py +++ b/mitmproxy/script/script.py @@ -4,14 +4,15 @@ Script objects know nothing about mitmproxy or mitmproxy's API - this knowledge by the mitmproxy-specific ScriptContext. """ # Do not import __future__ here, this would apply transitively to the inline scripts. +from __future__ import absolute_import, print_function, division + import os import shlex -import traceback import sys import six -from ..exceptions import ScriptException +from mitmproxy import exceptions class Script(object): @@ -42,7 +43,7 @@ class Script(object): @staticmethod def parse_command(command): if not command or not command.strip(): - raise ScriptException("Empty script command.") + raise exceptions.ScriptException("Empty script command.") # Windows: escape all backslashes in the path. if os.name == "nt": # pragma: no cover backslashes = shlex.split(command, posix=False)[0].count("\\") @@ -50,13 +51,13 @@ class Script(object): args = shlex.split(command) # pragma: no cover args[0] = os.path.expanduser(args[0]) if not os.path.exists(args[0]): - raise ScriptException( + raise exceptions.ScriptException( ("Script file not found: %s.\r\n" "If your script path contains spaces, " "make sure to wrap it in additional quotes, e.g. -s \"'./foo bar/baz.py' --args\".") % args[0]) elif os.path.isdir(args[0]): - raise ScriptException("Not a file: %s" % args[0]) + raise exceptions.ScriptException("Not a file: %s" % args[0]) return args def load(self): @@ -70,7 +71,7 @@ class Script(object): ScriptException on failure """ if self.ns is not None: - raise ScriptException("Script is already loaded") + raise exceptions.ScriptException("Script is already loaded") script_dir = os.path.dirname(os.path.abspath(self.args[0])) self.ns = {'__file__': os.path.abspath(self.args[0])} sys.path.append(script_dir) @@ -78,11 +79,11 @@ class Script(object): try: with open(self.filename) as f: code = compile(f.read(), self.filename, 'exec') - exec (code, self.ns, self.ns) + exec(code, self.ns, self.ns) except Exception: six.reraise( - ScriptException, - ScriptException.from_exception_context(), + exceptions.ScriptException, + exceptions.ScriptException.from_exception_context(), sys.exc_info()[2] ) finally: @@ -108,15 +109,15 @@ class Script(object): ScriptException if there was an exception. """ if self.ns is None: - raise ScriptException("Script not loaded.") + raise exceptions.ScriptException("Script not loaded.") f = self.ns.get(name) if f: try: return f(self.ctx, *args, **kwargs) except Exception: six.reraise( - ScriptException, - ScriptException.from_exception_context(), + exceptions.ScriptException, + exceptions.ScriptException.from_exception_context(), sys.exc_info()[2] ) else: diff --git a/mitmproxy/script/script_context.py b/mitmproxy/script/script_context.py index cd5d4b61..44e2736b 100644 --- a/mitmproxy/script/script_context.py +++ b/mitmproxy/script/script_context.py @@ -2,7 +2,8 @@ The mitmproxy script context provides an API to inline scripts. """ from __future__ import absolute_import, print_function, division -from .. import contentviews + +from mitmproxy import contentviews class ScriptContext(object): diff --git a/mitmproxy/stateobject.py b/mitmproxy/stateobject.py index 765c35d6..6283d845 100644 --- a/mitmproxy/stateobject.py +++ b/mitmproxy/stateobject.py @@ -1,9 +1,10 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function, division import six -from typing import List, Any +from typing import Any +from typing import List -from netlib.utils import Serializable +import netlib.basetypes def _is_list(cls): @@ -13,7 +14,7 @@ def _is_list(cls): return issubclass(cls, List) or is_list_bugfix -class StateObject(Serializable): +class StateObject(netlib.basetypes.Serializable): """ An object with serializable state. diff --git a/mitmproxy/tnetstring.py b/mitmproxy/tnetstring.py index d9d61258..6b1c117a 100644 --- a/mitmproxy/tnetstring.py +++ b/mitmproxy/tnetstring.py @@ -67,6 +67,8 @@ like so:: u'\u03b1' """ +from collections import deque + import six __ver_major__ = 0 @@ -77,9 +79,6 @@ __version__ = "%d.%d.%d%s" % ( __ver_major__, __ver_minor__, __ver_patch__, __ver_sub__) -from collections import deque - - def dumps(value, encoding=None): """dumps(object,encoding=None) -> string diff --git a/mitmproxy/utils.py b/mitmproxy/utils.py index e56ac473..680bc495 100644 --- a/mitmproxy/utils.py +++ b/mitmproxy/utils.py @@ -1,7 +1,8 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division + import datetime -import time import json +import time import netlib.utils @@ -24,32 +25,6 @@ def format_timestamp_with_milli(s): return d.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] -def isBin(s): - """ - Does this string have any non-ASCII characters? - """ - for i in s: - i = ord(i) - if i < 9 or 13 < i < 32 or 126 < i: - return True - return False - - -def isMostlyBin(s): - s = s[:100] - return sum(isBin(ch) for ch in s) / len(s) > 0.3 - - -def isXML(s): - for i in s: - if i in "\n \t": - continue - elif i == "<": - return True - else: - return False - - def pretty_json(s): try: p = json.loads(s) @@ -58,20 +33,6 @@ def pretty_json(s): return json.dumps(p, sort_keys=True, indent=4) -def pretty_duration(secs): - formatters = [ - (100, "{:.0f}s"), - (10, "{:2.1f}s"), - (1, "{:1.2f}s"), - ] - - for limit, formatter in formatters: - if secs >= limit: - return formatter.format(secs) - # less than 1 sec - return "{:.0f}ms".format(secs * 1000) - - pkg_data = netlib.utils.Data(__name__) @@ -105,44 +66,3 @@ class LRUCache: d = self.cacheList.pop() self.cache.pop(d) return ret - - -def clean_hanging_newline(t): - """ - Many editors will silently add a newline to the final line of a - document (I'm looking at you, Vim). This function fixes this common - problem at the risk of removing a hanging newline in the rare cases - where the user actually intends it. - """ - if t and t[-1] == "\n": - return t[:-1] - return t - - -def parse_size(s): - """ - Parses a size specification. Valid specifications are: - - 123: bytes - 123k: kilobytes - 123m: megabytes - 123g: gigabytes - """ - if not s: - return None - mult = None - if s[-1].lower() == "k": - mult = 1024**1 - elif s[-1].lower() == "m": - mult = 1024**2 - elif s[-1].lower() == "g": - mult = 1024**3 - - if mult: - s = s[:-1] - else: - mult = 1 - try: - return int(s) * mult - except ValueError: - raise ValueError("Invalid size specification: %s" % s) diff --git a/mitmproxy/version.py b/mitmproxy/version.py index da1e7229..0ebb0829 100644 --- a/mitmproxy/version.py +++ b/mitmproxy/version.py @@ -1,6 +1,13 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division from netlib.version import VERSION, IVERSION NAME = "mitmproxy" NAMEVERSION = NAME + " " + VERSION + +__all__ = [ + "NAME", + "NAMEVERSION", + "VERSION", + "IVERSION", +] diff --git a/mitmproxy/web/__init__.py b/mitmproxy/web/__init__.py index 956d221d..80a65886 100644 --- a/mitmproxy/web/__init__.py +++ b/mitmproxy/web/__init__.py @@ -1,14 +1,16 @@ -from __future__ import absolute_import, print_function +from __future__ import absolute_import, print_function, division + import collections -import tornado.ioloop -import tornado.httpserver import sys -from netlib.http import authentication +import tornado.httpserver +import tornado.ioloop -from .. import flow -from ..exceptions import FlowReadException -from . import app +from mitmproxy import controller +from mitmproxy import exceptions +from mitmproxy import flow +from mitmproxy.web import app +from netlib.http import authentication class Stop(Exception): @@ -156,7 +158,7 @@ class WebMaster(flow.FlowMaster): if options.rfile: try: self.load_flows_file(options.rfile) - except FlowReadException as v: + except exceptions.FlowReadException as v: self.add_event( "Could not read flow file: %s" % v, "error" @@ -194,19 +196,21 @@ class WebMaster(flow.FlowMaster): if self.state.intercept and self.state.intercept( f) and not f.request.is_replay: f.intercept(self) - else: - f.reply() + f.reply.take() - def handle_request(self, f): - super(WebMaster, self).handle_request(f) + @controller.handler + def request(self, f): + super(WebMaster, self).request(f) self._process_flow(f) - def handle_response(self, f): - super(WebMaster, self).handle_response(f) + @controller.handler + def response(self, f): + super(WebMaster, self).response(f) self._process_flow(f) - def handle_error(self, f): - super(WebMaster, self).handle_error(f) + @controller.handler + def error(self, f): + super(WebMaster, self).error(f) self._process_flow(f) def add_event(self, e, level="info"): diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py index 23a77cb0..43b2bad1 100644 --- a/mitmproxy/web/app.py +++ b/mitmproxy/web/app.py @@ -1,14 +1,16 @@ +from __future__ import absolute_import, print_function, division + +import base64 +import json +import logging import os.path import re import six -import tornado.web import tornado.websocket -import logging -import json -import base64 -from .. import version, filt +from mitmproxy import filt +from mitmproxy import version def _strip_content(flow_state): @@ -112,7 +114,8 @@ class RequestHandler(BasicAuth, tornado.web.RequestHandler): class IndexHandler(RequestHandler): def get(self): - _ = self.xsrf_token # https://github.com/tornadoweb/tornado/issues/645 + token = self.xsrf_token # https://github.com/tornadoweb/tornado/issues/645 + assert token self.render("index.html") @@ -282,11 +285,21 @@ class Events(RequestHandler): class Settings(RequestHandler): def get(self): + self.write(dict( data=dict( version=version.VERSION, mode=str(self.master.server.config.mode), - intercept=self.state.intercept_txt + intercept=self.state.intercept_txt, + showhost=self.master.options.showhost, + no_upstream_cert=self.master.server.config.no_upstream_cert, + rawtcp=self.master.server.config.rawtcp, + http2=self.master.server.config.http2, + anticache=self.master.options.anticache, + anticomp=self.master.options.anticomp, + stickyauth=self.master.stickyauth_txt, + stickycookie=self.master.stickycookie_txt, + stream= self.master.stream_large_bodies.max_size if self.master.stream_large_bodies else False ) )) @@ -296,6 +309,33 @@ class Settings(RequestHandler): if k == "intercept": self.state.set_intercept(v) update[k] = v + elif k == "showhost": + self.master.options.showhost = v + update[k] = v + elif k == "no_upstream_cert": + self.master.server.config.no_upstream_cert = v + update[k] = v + elif k == "rawtcp": + self.master.server.config.rawtcp = v + update[k] = v + elif k == "http2": + self.master.server.config.http2 = v + update[k] = v + elif k == "anticache": + self.master.options.anticache = v + update[k] = v + elif k == "anticomp": + self.master.options.anticomp = v + update[k] = v + elif k == "stickycookie": + self.master.set_stickycookie(v) + update[k] = v + elif k == "stickyauth": + self.master.set_stickyauth(v) + update[k] = v + elif k == "stream": + self.master.set_stream_large_bodies(v) + update[k] = v else: print("Warning: Unknown setting {}: {}".format(k, v)) diff --git a/mitmproxy/web/static/app.css b/mitmproxy/web/static/app.css index 824dd827..ae2e963f 100644 --- a/mitmproxy/web/static/app.css +++ b/mitmproxy/web/static/app.css @@ -146,6 +146,7 @@ header .menu { min-height: 1px; padding-left: 2.5px; padding-right: 2.5px; + margin-bottom: 5px; } @media (min-width: 768px) { .filter-input { @@ -163,8 +164,48 @@ header .menu { max-height: 500px; overflow-y: auto; } -.menu .btn { - margin: 2px 2px 2px 2px; +.menu .toggle-btn { + float: left; + width: 33.33333333%; + position: relative; + min-height: 1px; + padding-left: 2.5px; + padding-right: 2.5px; + margin-bottom: 5px; +} +@media (min-width: 768px) { + .menu .toggle-btn { + float: left; + width: 25%; + } +} +@media (min-width: 1200px) { + .menu .toggle-btn { + float: left; + width: 16.66666667%; + } +} +.menu .toggle-btn .btn { + width: 100%; +} +.menu .toggle-input-btn { + position: relative; + min-height: 1px; + padding-left: 2.5px; + padding-right: 2.5px; + margin-bottom: 5px; +} +@media (min-width: 768px) { + .menu .toggle-input-btn { + float: left; + width: 50%; + } +} +@media (min-width: 1200px) { + .menu .toggle-input-btn { + float: left; + width: 33.33333333%; + } } .flow-table { width: 100%; diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js index 1e8e313d..e02df55a 100644 --- a/mitmproxy/web/static/app.js +++ b/mitmproxy/web/static/app.js @@ -481,7 +481,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de Object.defineProperty(exports, "__esModule", { value: true }); -exports.ToggleComponent = exports.Splitter = exports.Router = undefined; +exports.ToggleInputButton = exports.ToggleButton = exports.Splitter = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require("react"); @@ -491,37 +493,19 @@ var _reactDom = require("react-dom"); var _reactDom2 = _interopRequireDefault(_reactDom); +var _utils = require("../utils.js"); + var _lodash = require("lodash"); var _lodash2 = _interopRequireDefault(_lodash); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -var Router = exports.Router = { - contextTypes: { - location: _react2.default.PropTypes.object, - router: _react2.default.PropTypes.object.isRequired - }, - updateLocation: function updateLocation(pathname, queryUpdate) { - if (pathname === undefined) { - pathname = this.context.location.pathname; - } - var query = this.context.location.query; - if (queryUpdate !== undefined) { - for (var i in queryUpdate) { - if (queryUpdate.hasOwnProperty(i)) { - query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. - } - } - } - this.context.router.replace({ pathname: pathname, query: query }); - }, - getQuery: function getQuery() { - // For whatever reason, react-router always returns the same object, which makes comparing - // the current props with nextProps impossible. As a workaround, we just clone the query object. - return _lodash2.default.clone(this.context.location.query); - } -}; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var Splitter = exports.Splitter = _react2.default.createClass({ displayName: "Splitter", @@ -631,28 +615,91 @@ var Splitter = exports.Splitter = _react2.default.createClass({ } }); -var ToggleComponent = exports.ToggleComponent = function ToggleComponent(props) { +var ToggleButton = exports.ToggleButton = function ToggleButton(props) { return _react2.default.createElement( "div", - { - className: "btn " + (props.checked ? "btn-primary" : "btn-default"), - onClick: props.onToggleChanged }, + { className: "input-group toggle-btn" }, _react2.default.createElement( - "span", - null, - _react2.default.createElement("i", { className: "fa " + (props.checked ? "fa-check-square-o" : "fa-square-o") }), - " ", - props.name + "div", + { + className: "btn " + (props.checked ? "btn-primary" : "btn-default"), + onClick: props.onToggleChanged }, + _react2.default.createElement( + "span", + { className: "fa " + (props.checked ? "fa-check-square-o" : "fa-square-o") }, + "Â ", + props.name + ) ) ); }; -ToggleComponent.propTypes = { +ToggleButton.propTypes = { + name: _react2.default.PropTypes.string.isRequired, + onToggleChanged: _react2.default.PropTypes.func.isRequired +}; + +var ToggleInputButton = exports.ToggleInputButton = function (_React$Component) { + _inherits(ToggleInputButton, _React$Component); + + function ToggleInputButton(props) { + _classCallCheck(this, ToggleInputButton); + + var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(ToggleInputButton).call(this, props)); + + _this.state = { txt: props.txt }; + return _this; + } + + _createClass(ToggleInputButton, [{ + key: "render", + value: function render() { + var _this2 = this; + + return _react2.default.createElement( + "div", + { className: "input-group toggle-input-btn" }, + _react2.default.createElement( + "span", + { + className: "input-group-btn", + onClick: function onClick() { + return _this2.props.onToggleChanged(_this2.state.txt); + } }, + _react2.default.createElement( + "div", + { className: "btn " + (this.props.checked ? "btn-primary" : "btn-default") }, + _react2.default.createElement("span", { className: "fa " + (this.props.checked ? "fa-check-square-o" : "fa-square-o") }), + "Â ", + this.props.name + ) + ), + _react2.default.createElement("input", { + className: "form-control", + placeholder: this.props.placeholder, + disabled: this.props.checked, + value: this.state.txt, + type: this.props.inputType, + onChange: function onChange(e) { + return _this2.setState({ txt: e.target.value }); + }, + onKeyDown: function onKeyDown(e) { + if (e.keyCode === _utils.Key.ENTER) _this2.props.onToggleChanged(_this2.state.txt);e.stopPropagation(); + } }) + ); + } + }]); + + return ToggleInputButton; +}(_react2.default.Component); + +ToggleInputButton.propTypes = { name: _react2.default.PropTypes.string.isRequired, + txt: _react2.default.PropTypes.string.isRequired, onToggleChanged: _react2.default.PropTypes.func.isRequired }; -},{"lodash":"lodash","react":"react","react-dom":"react-dom"}],5:[function(require,module,exports){ +},{"../utils.js":27,"lodash":"lodash","react":"react","react-dom":"react-dom"}],5:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -931,8 +978,6 @@ var _shallowequal = require("shallowequal"); var _shallowequal2 = _interopRequireDefault(_shallowequal); -var _common = require("./common.js"); - var _actions = require("../actions.js"); var _AutoScroll = require("./helpers/AutoScroll"); @@ -1120,8 +1165,6 @@ var AutoScrollEventLog = (0, _AutoScroll2.default)(EventLogContents); var EventLog = _react2.default.createClass({ displayName: "EventLog", - - mixins: [_common.Router], getInitialState: function getInitialState() { return { filter: { @@ -1134,7 +1177,7 @@ var EventLog = _react2.default.createClass({ close: function close() { var d = {}; d[_actions.Query.SHOW_EVENTLOG] = undefined; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, toggleLevel: function toggleLevel(level) { var filter = _lodash2.default.extend({}, this.state.filter); @@ -1165,7 +1208,7 @@ var EventLog = _react2.default.createClass({ exports.default = EventLog; -},{"../actions.js":2,"../store/view.js":26,"./common.js":4,"./helpers/AutoScroll":16,"./helpers/VirtualScroll":17,"lodash":"lodash","react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],7:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":26,"./helpers/AutoScroll":16,"./helpers/VirtualScroll":17,"lodash":"lodash","react":"react","react-dom":"react-dom","shallowequal":"shallowequal"}],7:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -1749,13 +1792,17 @@ var _utils2 = require("../../utils.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; var ViewImage = _react2.default.createClass({ displayName: "ViewImage", + propTypes: { + flow: _react2.default.PropTypes.object.isRequired, + message: _react2.default.PropTypes.object.isRequired + }, statics: { + regex: /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i, matches: function matches(message) { - return image_regex.test(_utils.MessageUtils.getContentType(message)); + return ViewImage.regex.test(_utils.MessageUtils.getContentType(message)); } }, render: function render() { @@ -1768,7 +1815,13 @@ var ViewImage = _react2.default.createClass({ } }); -var RawMixin = { +var ContentLoader = _react2.default.createClass({ + displayName: "ContentLoader", + + propTypes: { + flow: _react2.default.PropTypes.object.isRequired, + message: _react2.default.PropTypes.object.isRequired + }, getInitialState: function getInitialState() { return { content: undefined, @@ -1816,43 +1869,53 @@ var RawMixin = { _react2.default.createElement("i", { className: "fa fa-spinner fa-spin" }) ); } - return this.renderContent(); + return _react2.default.cloneElement(this.props.children, { + content: this.state.content + }); } -}; +}); var ViewRaw = _react2.default.createClass({ displayName: "ViewRaw", - mixins: [RawMixin], + propTypes: { + content: _react2.default.PropTypes.string.isRequired + }, statics: { + textView: true, matches: function matches(message) { return true; } }, - renderContent: function renderContent() { + render: function render() { return _react2.default.createElement( "pre", null, - this.state.content + this.props.content ); } }); -var json_regex = /^application\/json$/i; var ViewJSON = _react2.default.createClass({ displayName: "ViewJSON", - mixins: [RawMixin], + propTypes: { + content: _react2.default.PropTypes.string.isRequired + }, statics: { + textView: true, + regex: /^application\/json$/i, matches: function matches(message) { - return json_regex.test(_utils.MessageUtils.getContentType(message)); + return ViewJSON.regex.test(_utils.MessageUtils.getContentType(message)); } }, - renderContent: function renderContent() { - var json = this.state.content; + render: function render() { + var json = this.props.content; try { json = JSON.stringify(JSON.parse(json), null, 2); - } catch (e) {} + } catch (e) { + // @noop + } return _react2.default.createElement( "pre", null, @@ -1864,6 +1927,10 @@ var ViewJSON = _react2.default.createClass({ var ViewAuto = _react2.default.createClass({ displayName: "ViewAuto", + propTypes: { + message: _react2.default.PropTypes.object.isRequired, + flow: _react2.default.PropTypes.object.isRequired + }, statics: { matches: function matches() { return false; // don't match itself @@ -1878,8 +1945,20 @@ var ViewAuto = _react2.default.createClass({ } }, render: function render() { + var _props = this.props; + var message = _props.message; + var flow = _props.flow; + var View = ViewAuto.findView(this.props.message); - return _react2.default.createElement(View, this.props); + if (View.textView) { + return _react2.default.createElement( + ContentLoader, + { message: message, flow: flow }, + _react2.default.createElement(View, { content: "" }) + ); + } else { + return _react2.default.createElement(View, { message: message, flow: flow }); + } } }); @@ -2004,6 +2083,10 @@ var ContentView = _react2.default.createClass({ } }, render: function render() { + var _props2 = this.props; + var flow = _props2.flow; + var message = _props2.message; + var message = this.props.message; if (message.contentLength === 0) { return _react2.default.createElement(ContentEmpty, this.props); @@ -2018,7 +2101,11 @@ var ContentView = _react2.default.createClass({ return _react2.default.createElement( "div", null, - _react2.default.createElement(this.state.View, this.props), + this.state.View.textView ? _react2.default.createElement( + ContentLoader, + { flow: flow, message: message }, + _react2.default.createElement(this.state.View, { content: "" }) + ) : _react2.default.createElement(this.state.View, { flow: flow, message: message }), _react2.default.createElement( "div", { className: "view-options text-center" }, @@ -2315,8 +2402,6 @@ var _react = require("react"); var _react2 = _interopRequireDefault(_react); -var _common = require("../common.js"); - var _nav = require("./nav.js"); var _nav2 = _interopRequireDefault(_nav); @@ -2343,7 +2428,6 @@ var allTabs = { var FlowView = _react2.default.createClass({ displayName: "FlowView", - mixins: [_common.StickyHeadMixin, _common.Router], getInitialState: function getInitialState() { return { prompt: false @@ -2367,7 +2451,7 @@ var FlowView = _react2.default.createClass({ this.selectTab(tabs[nextIndex]); }, selectTab: function selectTab(panel) { - this.updateLocation("/flows/" + this.props.flow.id + "/" + panel); + this.props.updateLocation("/flows/" + this.props.flow.id + "/" + panel); }, promptEdit: function promptEdit() { var options; @@ -2436,7 +2520,7 @@ var FlowView = _react2.default.createClass({ exports.default = FlowView; -},{"../common.js":4,"../prompt.js":19,"./details.js":10,"./messages.js":12,"./nav.js":13,"react":"react"}],12:[function(require,module,exports){ +},{"../prompt.js":19,"./details.js":10,"./messages.js":12,"./nav.js":13,"react":"react"}],12:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { @@ -2894,6 +2978,8 @@ var _react = require("react"); var _react2 = _interopRequireDefault(_react); +var _utils = require("../utils.js"); + var _common = require("./common.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -2906,6 +2992,15 @@ function Footer(_ref) { var settings = _ref.settings; var mode = settings.mode; var intercept = settings.intercept; + var showhost = settings.showhost; + var no_upstream_cert = settings.no_upstream_cert; + var rawtcp = settings.rawtcp; + var http2 = settings.http2; + var anticache = settings.anticache; + var anticomp = settings.anticomp; + var stickyauth = settings.stickyauth; + var stickycookie = settings.stickycookie; + var stream = settings.stream; return _react2.default.createElement( "footer", @@ -2921,19 +3016,65 @@ function Footer(_ref) { { className: "label label-success" }, "Intercept: ", intercept + ), + showhost && _react2.default.createElement( + "span", + { className: "label label-success" }, + "showhost" + ), + no_upstream_cert && _react2.default.createElement( + "span", + { className: "label label-success" }, + "no-upstream-cert" + ), + rawtcp && _react2.default.createElement( + "span", + { className: "label label-success" }, + "raw-tcp" + ), + !http2 && _react2.default.createElement( + "span", + { className: "label label-success" }, + "no-http2" + ), + anticache && _react2.default.createElement( + "span", + { className: "label label-success" }, + "anticache" + ), + anticomp && _react2.default.createElement( + "span", + { className: "label label-success" }, + "anticomp" + ), + stickyauth && _react2.default.createElement( + "span", + { className: "label label-success" }, + "stickyauth: ", + stickyauth + ), + stickycookie && _react2.default.createElement( + "span", + { className: "label label-success" }, + "stickycookie: ", + stickycookie + ), + stream && _react2.default.createElement( + "span", + { className: "label label-success" }, + "stream: ", + (0, _utils.formatSize)(stream) ) ); } -},{"./common.js":4,"react":"react"}],15:[function(require,module,exports){ +},{"../utils.js":27,"./common.js":4,"react":"react"}],15:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Header = exports.MainMenu = undefined; - -var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); +exports.Header = exports.OptionMenu = exports.MainMenu = undefined; var _react = require("react"); @@ -2959,12 +3100,6 @@ var _actions = require("../actions.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } - var FilterDocs = _react2.default.createClass({ displayName: "FilterDocs", @@ -3151,7 +3286,6 @@ var FilterInput = _react2.default.createClass({ var MainMenu = exports.MainMenu = _react2.default.createClass({ displayName: "MainMenu", - mixins: [_common.Router], propTypes: { settings: _react2.default.PropTypes.object.isRequired }, @@ -3162,19 +3296,19 @@ var MainMenu = exports.MainMenu = _react2.default.createClass({ onSearchChange: function onSearchChange(val) { var d = {}; d[_actions.Query.SEARCH] = val; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, onHighlightChange: function onHighlightChange(val) { var d = {}; d[_actions.Query.HIGHLIGHT] = val; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, onInterceptChange: function onInterceptChange(val) { _actions.SettingsActions.update({ intercept: val }); }, render: function render() { - var search = this.getQuery()[_actions.Query.SEARCH] || ""; - var highlight = this.getQuery()[_actions.Query.HIGHLIGHT] || ""; + var search = this.props.query[_actions.Query.SEARCH] || ""; + var highlight = this.props.query[_actions.Query.HIGHLIGHT] || ""; var intercept = this.props.settings.intercept || ""; return _react2.default.createElement( @@ -3217,78 +3351,122 @@ var ViewMenu = _react2.default.createClass({ title: "View", route: "flows" }, - mixins: [_common.Router], toggleEventLog: function toggleEventLog() { var d = {}; - if (this.getQuery()[_actions.Query.SHOW_EVENTLOG]) { + if (this.props.query[_actions.Query.SHOW_EVENTLOG]) { d[_actions.Query.SHOW_EVENTLOG] = undefined; } else { d[_actions.Query.SHOW_EVENTLOG] = "t"; // any non-false value will do it, keep it short } - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); console.log('toggleevent'); }, render: function render() { - var showEventLog = this.getQuery()[_actions.Query.SHOW_EVENTLOG]; + var showEventLog = this.props.query[_actions.Query.SHOW_EVENTLOG]; return _react2.default.createElement( "div", null, - _react2.default.createElement(_common.ToggleComponent, { - checked: showEventLog, - name: "Show Eventlog", - onToggleChanged: this.toggleEventLog }) + _react2.default.createElement( + "div", + { className: "menu-row" }, + _react2.default.createElement(_common.ToggleButton, { + checked: showEventLog, + name: "Show Eventlog", + onToggleChanged: this.toggleEventLog }) + ), + _react2.default.createElement("div", { className: "clearfix" }) ); } }); -var OptionMenu = function (_React$Component) { - _inherits(OptionMenu, _React$Component); - - function OptionMenu(props) { - _classCallCheck(this, OptionMenu); - - var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(OptionMenu).call(this, props)); - - _this.state = { - options: [{ name: "--host", checked: true }, { name: "--no-upstream-cert", checked: false }, { name: "--http2", checked: false }, { name: "--anticache", checked: false }, { name: "--anticomp", checked: false }, { name: "--stickycookie", checked: true }, { name: "--stickyauth", checked: false }, { name: "--stream", checked: false }] - }; - return _this; - } - - _createClass(OptionMenu, [{ - key: "setOption", - value: function setOption(entry) { - console.log(entry.name); //TODO: get options from outside and remove state - entry.checked = !entry.checked; - this.setState({ options: this.state.options }); - } - }, { - key: "render", - value: function render() { - var _this2 = this; - - return _react2.default.createElement( - "div", - null, - this.state.options.map(function (entry, i) { - return _react2.default.createElement(_common.ToggleComponent, { - key: i, - checked: entry.checked, - name: entry.name, - onToggleChanged: function onToggleChanged() { - return _this2.setOption(entry); - } }); - }) - ); - } - }]); - - return OptionMenu; -}(_react2.default.Component); +var OptionMenu = exports.OptionMenu = function OptionMenu(props) { + var _props$settings = props.settings; + var mode = _props$settings.mode; + var intercept = _props$settings.intercept; + var showhost = _props$settings.showhost; + var no_upstream_cert = _props$settings.no_upstream_cert; + var rawtcp = _props$settings.rawtcp; + var http2 = _props$settings.http2; + var anticache = _props$settings.anticache; + var anticomp = _props$settings.anticomp; + var stickycookie = _props$settings.stickycookie; + var stickyauth = _props$settings.stickyauth; + var stream = _props$settings.stream; + return _react2.default.createElement( + "div", + null, + _react2.default.createElement( + "div", + { className: "menu-row" }, + _react2.default.createElement(_common.ToggleButton, { name: "showhost", + checked: showhost, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ showhost: !showhost }); + } + }), + _react2.default.createElement(_common.ToggleButton, { name: "no_upstream_cert", + checked: no_upstream_cert, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ no_upstream_cert: !no_upstream_cert }); + } + }), + _react2.default.createElement(_common.ToggleButton, { name: "rawtcp", + checked: rawtcp, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ rawtcp: !rawtcp }); + } + }), + _react2.default.createElement(_common.ToggleButton, { name: "http2", + checked: http2, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ http2: !http2 }); + } + }), + _react2.default.createElement(_common.ToggleButton, { name: "anticache", + checked: anticache, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ anticache: !anticache }); + } + }), + _react2.default.createElement(_common.ToggleButton, { name: "anticomp", + checked: anticomp, + onToggleChanged: function onToggleChanged() { + return _actions.SettingsActions.update({ anticomp: !anticomp }); + } + }), + _react2.default.createElement(_common.ToggleInputButton, { name: "stickyauth", placeholder: "Sticky auth filter", + checked: Boolean(stickyauth), + txt: stickyauth || "", + onToggleChanged: function onToggleChanged(txt) { + return _actions.SettingsActions.update({ stickyauth: !stickyauth ? txt : null }); + } + }), + _react2.default.createElement(_common.ToggleInputButton, { name: "stickycookie", placeholder: "Sticky cookie filter", + checked: Boolean(stickycookie), + txt: stickycookie || "", + onToggleChanged: function onToggleChanged(txt) { + return _actions.SettingsActions.update({ stickycookie: !stickycookie ? txt : null }); + } + }), + _react2.default.createElement(_common.ToggleInputButton, { name: "stream", placeholder: "stream...", + checked: Boolean(stream), + txt: stream || "", + inputType: "number", + onToggleChanged: function onToggleChanged(txt) { + return _actions.SettingsActions.update({ stream: !stream ? txt : null }); + } + }) + ), + _react2.default.createElement("div", { className: "clearfix" }) + ); +}; OptionMenu.title = "Options"; +OptionMenu.propTypes = { + settings: _react2.default.PropTypes.object.isRequired +}; var ReportsMenu = _react2.default.createClass({ displayName: "ReportsMenu", @@ -3391,7 +3569,6 @@ var header_entries = [MainMenu, ViewMenu, OptionMenu /*, ReportsMenu */]; var Header = exports.Header = _react2.default.createClass({ displayName: "Header", - mixins: [_common.Router], propTypes: { settings: _react2.default.PropTypes.object.isRequired }, @@ -3402,7 +3579,7 @@ var Header = exports.Header = _react2.default.createClass({ }, handleClick: function handleClick(active, e) { e.preventDefault(); - this.updateLocation(active.route); + this.props.updateLocation(active.route); this.setState({ active: active }); }, render: function render() { @@ -3435,7 +3612,11 @@ var Header = exports.Header = _react2.default.createClass({ _react2.default.createElement( "div", { className: "menu" }, - _react2.default.createElement(this.state.active, { ref: "active", settings: this.props.settings }) + _react2.default.createElement(this.state.active, { + settings: this.props.settings, + updateLocation: this.props.updateLocation, + query: this.props.query + }) ) ); } @@ -3622,7 +3803,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de var MainView = _react2.default.createClass({ displayName: "MainView", - mixins: [_common.Router], contextTypes: { flowStore: _react2.default.PropTypes.object.isRequired }, @@ -3653,11 +3833,11 @@ var MainView = _react2.default.createClass({ }, getViewFilt: function getViewFilt() { try { - var filtStr = this.getQuery()[_actions.Query.SEARCH]; + var filtStr = this.props.query[_actions.Query.SEARCH]; var filt = filtStr ? _filt2.default.parse(filtStr) : function () { return true; }; - var highlightStr = this.getQuery()[_actions.Query.HIGHLIGHT]; + var highlightStr = this.props.query[_actions.Query.HIGHLIGHT]; var highlight = highlightStr ? _filt2.default.parse(highlightStr) : function () { return false; }; @@ -3710,10 +3890,10 @@ var MainView = _react2.default.createClass({ selectFlow: function selectFlow(flow) { if (flow) { var tab = this.props.routeParams.detailTab || "request"; - this.updateLocation("/flows/" + flow.id + "/" + tab); + this.props.updateLocation("/flows/" + flow.id + "/" + tab); this.refs.flowTable.scrollIntoView(flow); } else { - this.updateLocation("/flows"); + this.props.updateLocation("/flows"); } }, selectFlowRelative: function selectFlowRelative(shift) { @@ -3837,6 +4017,8 @@ var MainView = _react2.default.createClass({ key: "flowDetails", ref: "flowDetails", tab: this.props.routeParams.detailTab, + query: this.props.query, + updateLocation: this.props.updateLocation, flow: selected })]; } else { details = null; @@ -4054,13 +4236,34 @@ var Reports = _react2.default.createClass({ var ProxyAppMain = _react2.default.createClass({ displayName: "ProxyAppMain", - mixins: [_common.Router], childContextTypes: { flowStore: _react2.default.PropTypes.object.isRequired, eventStore: _react2.default.PropTypes.object.isRequired, returnFocus: _react2.default.PropTypes.func.isRequired, location: _react2.default.PropTypes.object.isRequired }, + contextTypes: { + router: _react2.default.PropTypes.object.isRequired + }, + updateLocation: function updateLocation(pathname, queryUpdate) { + if (pathname === undefined) { + pathname = this.props.location.pathname; + } + var query = this.props.location.query; + if (queryUpdate !== undefined) { + for (var i in queryUpdate) { + if (queryUpdate.hasOwnProperty(i)) { + query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. + } + } + } + this.context.router.replace({ pathname: pathname, query: query }); + }, + getQuery: function getQuery() { + // For whatever reason, react-router always returns the same object, which makes comparing + // the current props with nextProps impossible. As a workaround, we just clone the query object. + return _lodash2.default.clone(this.props.location.query); + }, componentDidMount: function componentDidMount() { this.focus(); this.settingsStore.addListener("recalculate", this.onSettingsChange); @@ -4130,18 +4333,18 @@ var ProxyAppMain = _react2.default.createClass({ e.preventDefault(); }, render: function render() { + var query = this.getQuery(); var eventlog; if (this.props.location.query[_actions.Query.SHOW_EVENTLOG]) { - eventlog = [_react2.default.createElement(_common.Splitter, { key: "splitter", axis: "y" }), _react2.default.createElement(_eventlog2.default, { key: "eventlog" })]; + eventlog = [_react2.default.createElement(_common.Splitter, { key: "splitter", axis: "y" }), _react2.default.createElement(_eventlog2.default, { key: "eventlog", updateLocation: this.updateLocation })]; } else { eventlog = null; } - var children = _react2.default.cloneElement(this.props.children, { ref: "view", location: this.props.location }); return _react2.default.createElement( "div", { id: "container", tabIndex: "0", onKeyDown: this.onKeydown }, - _react2.default.createElement(_header.Header, { ref: "header", settings: this.state.settings }), - children, + _react2.default.createElement(_header.Header, { ref: "header", settings: this.state.settings, updateLocation: this.updateLocation, query: query }), + _react2.default.cloneElement(this.props.children, { ref: "view", location: this.props.location, updateLocation: this.updateLocation, query: query }), eventlog, _react2.default.createElement(_footer2.default, { settings: this.state.settings }) ); diff --git a/mitmproxy/web/static/vendor.js b/mitmproxy/web/static/vendor.js index 44bc86d8..efe6f2b4 100644 --- a/mitmproxy/web/static/vendor.js +++ b/mitmproxy/web/static/vendor.js @@ -2116,15 +2116,13 @@ var KNOWN_STATICS = { }; module.exports = function hoistNonReactStatics(targetComponent, sourceComponent) { - if (typeof sourceComponent !== 'string') { // don't hoist over string (html) components - var keys = Object.getOwnPropertyNames(sourceComponent); - for (var i=0; i<keys.length; ++i) { - if (!REACT_STATICS[keys[i]] && !KNOWN_STATICS[keys[i]]) { - try { - targetComponent[keys[i]] = sourceComponent[keys[i]]; - } catch (error) { - - } + var keys = Object.getOwnPropertyNames(sourceComponent); + for (var i=0; i<keys.length; ++i) { + if (!REACT_STATICS[keys[i]] && !KNOWN_STATICS[keys[i]]) { + try { + targetComponent[keys[i]] = sourceComponent[keys[i]]; + } catch (error) { + } } } @@ -3087,9 +3085,6 @@ var currentQueue; var queueIndex = -1; function cleanUpNextTick() { - if (!draining || !currentQueue) { - return; - } draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); @@ -35722,7 +35717,8 @@ return jQuery; (function (global){ /** * @license - * lodash <https://lodash.com/> + * lodash 4.11.2 (Custom Build) <https://lodash.com/> + * Build: `lodash -d -o ./foo/lodash.js` * Copyright jQuery Foundation and other contributors <https://jquery.org/> * Released under MIT license <https://lodash.com/license> * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> @@ -35734,7 +35730,7 @@ return jQuery; var undefined; /** Used as the semantic version number. */ - var VERSION = '4.12.0'; + var VERSION = '4.11.2'; /** Used as the size to enable large array optimizations. */ var LARGE_ARRAY_SIZE = 200; @@ -36179,6 +36175,30 @@ return jQuery; } /** + * Creates a new array concatenating `array` with `other`. + * + * @private + * @param {Array} array The first array to concatenate. + * @param {Array} other The second array to concatenate. + * @returns {Array} Returns the new concatenated array. + */ + function arrayConcat(array, other) { + var index = -1, + length = array.length, + othIndex = -1, + othLength = other.length, + result = Array(length + othLength); + + while (++index < length) { + result[index] = array[index]; + } + while (++othIndex < othLength) { + result[index++] = other[othIndex]; + } + return result; + } + + /** * A specialized version of `_.forEach` for arrays without support for * iteratee shorthands. * @@ -36605,7 +36625,7 @@ return jQuery; * @private * @param {Object} object The object to query. * @param {Array} props The property names to get values for. - * @returns {Object} Returns the key-value pairs. + * @returns {Object} Returns the new array of key-value pairs. */ function baseToPairs(object, props) { return arrayMap(props, function(key) { @@ -36618,7 +36638,7 @@ return jQuery; * * @private * @param {Function} func The function to cap arguments for. - * @returns {Function} Returns the new capped function. + * @returns {Function} Returns the new function. */ function baseUnary(func) { return function(value) { @@ -36643,18 +36663,6 @@ return jQuery; } /** - * Checks if a cache value for `key` exists. - * - * @private - * @param {Object} cache The cache to query. - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function cacheHas(cache, key) { - return cache.has(key); - } - - /** * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol * that is not found in the character symbols. * @@ -36810,11 +36818,11 @@ return jQuery; } /** - * Converts `map` to its key-value pairs. + * Converts `map` to an array. * * @private * @param {Object} map The map to convert. - * @returns {Array} Returns the key-value pairs. + * @returns {Array} Returns the converted array. */ function mapToArray(map) { var index = -1, @@ -36852,11 +36860,11 @@ return jQuery; } /** - * Converts `set` to an array of its values. + * Converts `set` to an array. * * @private * @param {Object} set The set to convert. - * @returns {Array} Returns the values. + * @returns {Array} Returns the converted array. */ function setToArray(set) { var index = -1, @@ -36869,23 +36877,6 @@ return jQuery; } /** - * Converts `set` to its value-value pairs. - * - * @private - * @param {Object} set The set to convert. - * @returns {Array} Returns the value-value pairs. - */ - function setToPairs(set) { - var index = -1, - result = Array(set.size); - - set.forEach(function(value) { - result[++index] = [value, value]; - }); - return result; - } - - /** * Gets the number of symbols in `string`. * * @private @@ -37138,10 +37129,10 @@ return jQuery; * `floor`, `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, * `forOwnRight`, `get`, `gt`, `gte`, `has`, `hasIn`, `head`, `identity`, * `includes`, `indexOf`, `inRange`, `invoke`, `isArguments`, `isArray`, - * `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, `isBoolean`, - * `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`, `isEqualWith`, - * `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`, `isMap`, - * `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, `isNumber`, + * `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, `isBoolean`, `isBuffer`, + * `isDate`, `isElement`, `isEmpty`, `isEqual`, `isEqualWith`, `isError`, + * `isFinite`, `isFunction`, `isInteger`, `isLength`, `isMap`, `isMatch`, + * `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, `isNumber`, * `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, `isSafeInteger`, * `isSet`, `isString`, `isUndefined`, `isTypedArray`, `isWeakMap`, `isWeakSet`, * `join`, `kebabCase`, `last`, `lastIndexOf`, `lowerCase`, `lowerFirst`, @@ -37150,9 +37141,9 @@ return jQuery; * `pop`, `random`, `reduce`, `reduceRight`, `repeat`, `result`, `round`, * `runInContext`, `sample`, `shift`, `size`, `snakeCase`, `some`, `sortedIndex`, * `sortedIndexBy`, `sortedLastIndex`, `sortedLastIndexBy`, `startCase`, - * `startsWith`, `subtract`, `sum`, `sumBy`, `template`, `times`, `toFinite`, - * `toInteger`, `toJSON`, `toLength`, `toLower`, `toNumber`, `toSafeInteger`, - * `toString`, `toUpper`, `trim`, `trimEnd`, `trimStart`, `truncate`, `unescape`, + * `startsWith`, `subtract`, `sum`, `sumBy`, `template`, `times`, `toInteger`, + * `toJSON`, `toLength`, `toLower`, `toNumber`, `toSafeInteger`, `toString`, + * `toUpper`, `trim`, `trimEnd`, `trimStart`, `truncate`, `unescape`, * `uniqueId`, `upperCase`, `upperFirst`, `value`, and `words` * * @name _ @@ -37412,212 +37403,64 @@ return jQuery; * * @private * @constructor - * @param {Array} [entries] The key-value pairs to cache. + * @returns {Object} Returns the new hash object. */ - function Hash(entries) { - var index = -1, - length = entries ? entries.length : 0; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the hash. - * - * @private - * @name clear - * @memberOf Hash - */ - function hashClear() { - this.__data__ = nativeCreate ? nativeCreate(null) : {}; - } + function Hash() {} /** * Removes `key` and its value from the hash. * * @private - * @name delete - * @memberOf Hash * @param {Object} hash The hash to modify. * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ - function hashDelete(key) { - return this.has(key) && delete this.__data__[key]; + function hashDelete(hash, key) { + return hashHas(hash, key) && delete hash[key]; } /** * Gets the hash value for `key`. * * @private - * @name get - * @memberOf Hash + * @param {Object} hash The hash to query. * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ - function hashGet(key) { - var data = this.__data__; + function hashGet(hash, key) { if (nativeCreate) { - var result = data[key]; + var result = hash[key]; return result === HASH_UNDEFINED ? undefined : result; } - return hasOwnProperty.call(data, key) ? data[key] : undefined; + return hasOwnProperty.call(hash, key) ? hash[key] : undefined; } /** * Checks if a hash value for `key` exists. * * @private - * @name has - * @memberOf Hash + * @param {Object} hash The hash to query. * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ - function hashHas(key) { - var data = this.__data__; - return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key); + function hashHas(hash, key) { + return nativeCreate ? hash[key] !== undefined : hasOwnProperty.call(hash, key); } /** * Sets the hash `key` to `value`. * * @private - * @name set - * @memberOf Hash - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the hash instance. - */ - function hashSet(key, value) { - var data = this.__data__; - data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; - return this; - } - - // Add methods to `Hash`. - Hash.prototype.clear = hashClear; - Hash.prototype['delete'] = hashDelete; - Hash.prototype.get = hashGet; - Hash.prototype.has = hashHas; - Hash.prototype.set = hashSet; - - /*------------------------------------------------------------------------*/ - - /** - * Creates an list cache object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function ListCache(entries) { - var index = -1, - length = entries ? entries.length : 0; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the list cache. - * - * @private - * @name clear - * @memberOf ListCache - */ - function listCacheClear() { - this.__data__ = []; - } - - /** - * Removes `key` and its value from the list cache. - * - * @private - * @name delete - * @memberOf ListCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function listCacheDelete(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - return false; - } - var lastIndex = data.length - 1; - if (index == lastIndex) { - data.pop(); - } else { - splice.call(data, index, 1); - } - return true; - } - - /** - * Gets the list cache value for `key`. - * - * @private - * @name get - * @memberOf ListCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function listCacheGet(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - return index < 0 ? undefined : data[index][1]; - } - - /** - * Checks if a list cache value for `key` exists. - * - * @private - * @name has - * @memberOf ListCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function listCacheHas(key) { - return assocIndexOf(this.__data__, key) > -1; - } - - /** - * Sets the list cache `key` to `value`. - * - * @private - * @name set - * @memberOf ListCache + * @param {Object} hash The hash to modify. * @param {string} key The key of the value to set. * @param {*} value The value to set. - * @returns {Object} Returns the list cache instance. */ - function listCacheSet(key, value) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - data.push([key, value]); - } else { - data[index][1] = value; - } - return this; + function hashSet(hash, key, value) { + hash[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; } - // Add methods to `ListCache`. - ListCache.prototype.clear = listCacheClear; - ListCache.prototype['delete'] = listCacheDelete; - ListCache.prototype.get = listCacheGet; - ListCache.prototype.has = listCacheHas; - ListCache.prototype.set = listCacheSet; + // Avoid inheriting from `Object.prototype` when possible. + Hash.prototype = nativeCreate ? nativeCreate(null) : objectProto; /*------------------------------------------------------------------------*/ @@ -37626,15 +37469,15 @@ return jQuery; * * @private * @constructor - * @param {Array} [entries] The key-value pairs to cache. + * @param {Array} [values] The values to cache. */ - function MapCache(entries) { + function MapCache(values) { var index = -1, - length = entries ? entries.length : 0; + length = values ? values.length : 0; this.clear(); while (++index < length) { - var entry = entries[index]; + var entry = values[index]; this.set(entry[0], entry[1]); } } @@ -37646,10 +37489,10 @@ return jQuery; * @name clear * @memberOf MapCache */ - function mapCacheClear() { + function mapClear() { this.__data__ = { 'hash': new Hash, - 'map': new (Map || ListCache), + 'map': Map ? new Map : [], 'string': new Hash }; } @@ -37663,8 +37506,12 @@ return jQuery; * @param {string} key The key of the value to remove. * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ - function mapCacheDelete(key) { - return getMapData(this, key)['delete'](key); + function mapDelete(key) { + var data = this.__data__; + if (isKeyable(key)) { + return hashDelete(typeof key == 'string' ? data.string : data.hash, key); + } + return Map ? data.map['delete'](key) : assocDelete(data.map, key); } /** @@ -37676,8 +37523,12 @@ return jQuery; * @param {string} key The key of the value to get. * @returns {*} Returns the entry value. */ - function mapCacheGet(key) { - return getMapData(this, key).get(key); + function mapGet(key) { + var data = this.__data__; + if (isKeyable(key)) { + return hashGet(typeof key == 'string' ? data.string : data.hash, key); + } + return Map ? data.map.get(key) : assocGet(data.map, key); } /** @@ -37689,8 +37540,12 @@ return jQuery; * @param {string} key The key of the entry to check. * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ - function mapCacheHas(key) { - return getMapData(this, key).has(key); + function mapHas(key) { + var data = this.__data__; + if (isKeyable(key)) { + return hashHas(typeof key == 'string' ? data.string : data.hash, key); + } + return Map ? data.map.has(key) : assocHas(data.map, key); } /** @@ -37703,23 +37558,30 @@ return jQuery; * @param {*} value The value to set. * @returns {Object} Returns the map cache instance. */ - function mapCacheSet(key, value) { - getMapData(this, key).set(key, value); + function mapSet(key, value) { + var data = this.__data__; + if (isKeyable(key)) { + hashSet(typeof key == 'string' ? data.string : data.hash, key, value); + } else if (Map) { + data.map.set(key, value); + } else { + assocSet(data.map, key, value); + } return this; } // Add methods to `MapCache`. - MapCache.prototype.clear = mapCacheClear; - MapCache.prototype['delete'] = mapCacheDelete; - MapCache.prototype.get = mapCacheGet; - MapCache.prototype.has = mapCacheHas; - MapCache.prototype.set = mapCacheSet; + MapCache.prototype.clear = mapClear; + MapCache.prototype['delete'] = mapDelete; + MapCache.prototype.get = mapGet; + MapCache.prototype.has = mapHas; + MapCache.prototype.set = mapSet; /*------------------------------------------------------------------------*/ /** * - * Creates an array cache object to store unique values. + * Creates a set cache object to store unique values. * * @private * @constructor @@ -37731,41 +37593,52 @@ return jQuery; this.__data__ = new MapCache; while (++index < length) { - this.add(values[index]); + this.push(values[index]); } } /** - * Adds `value` to the array cache. + * Checks if `value` is in `cache`. * * @private - * @name add - * @memberOf SetCache - * @alias push - * @param {*} value The value to cache. - * @returns {Object} Returns the cache instance. + * @param {Object} cache The set cache to search. + * @param {*} value The value to search for. + * @returns {number} Returns `true` if `value` is found, else `false`. */ - function setCacheAdd(value) { - this.__data__.set(value, HASH_UNDEFINED); - return this; + function cacheHas(cache, value) { + var map = cache.__data__; + if (isKeyable(value)) { + var data = map.__data__, + hash = typeof value == 'string' ? data.string : data.hash; + + return hash[value] === HASH_UNDEFINED; + } + return map.has(value); } /** - * Checks if `value` is in the array cache. + * Adds `value` to the set cache. * * @private - * @name has + * @name push * @memberOf SetCache - * @param {*} value The value to search for. - * @returns {number} Returns `true` if `value` is found, else `false`. + * @param {*} value The value to cache. */ - function setCacheHas(value) { - return this.__data__.has(value); + function cachePush(value) { + var map = this.__data__; + if (isKeyable(value)) { + var data = map.__data__, + hash = typeof value == 'string' ? data.string : data.hash; + + hash[value] = HASH_UNDEFINED; + } + else { + map.set(value, HASH_UNDEFINED); + } } // Add methods to `SetCache`. - SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; - SetCache.prototype.has = setCacheHas; + SetCache.prototype.push = cachePush; /*------------------------------------------------------------------------*/ @@ -37774,10 +37647,17 @@ return jQuery; * * @private * @constructor - * @param {Array} [entries] The key-value pairs to cache. + * @param {Array} [values] The values to cache. */ - function Stack(entries) { - this.__data__ = new ListCache(entries); + function Stack(values) { + var index = -1, + length = values ? values.length : 0; + + this.clear(); + while (++index < length) { + var entry = values[index]; + this.set(entry[0], entry[1]); + } } /** @@ -37788,7 +37668,7 @@ return jQuery; * @memberOf Stack */ function stackClear() { - this.__data__ = new ListCache; + this.__data__ = { 'array': [], 'map': null }; } /** @@ -37801,7 +37681,10 @@ return jQuery; * @returns {boolean} Returns `true` if the entry was removed, else `false`. */ function stackDelete(key) { - return this.__data__['delete'](key); + var data = this.__data__, + array = data.array; + + return array ? assocDelete(array, key) : data.map['delete'](key); } /** @@ -37814,7 +37697,10 @@ return jQuery; * @returns {*} Returns the entry value. */ function stackGet(key) { - return this.__data__.get(key); + var data = this.__data__, + array = data.array; + + return array ? assocGet(array, key) : data.map.get(key); } /** @@ -37827,7 +37713,10 @@ return jQuery; * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. */ function stackHas(key) { - return this.__data__.has(key); + var data = this.__data__, + array = data.array; + + return array ? assocHas(array, key) : data.map.has(key); } /** @@ -37841,11 +37730,21 @@ return jQuery; * @returns {Object} Returns the stack cache instance. */ function stackSet(key, value) { - var cache = this.__data__; - if (cache instanceof ListCache && cache.__data__.length == LARGE_ARRAY_SIZE) { - cache = this.__data__ = new MapCache(cache.__data__); + var data = this.__data__, + array = data.array; + + if (array) { + if (array.length < (LARGE_ARRAY_SIZE - 1)) { + assocSet(array, key, value); + } else { + data.array = null; + data.map = new MapCache(array); + } + } + var map = data.map; + if (map) { + map.set(key, value); } - cache.set(key, value); return this; } @@ -37859,6 +37758,90 @@ return jQuery; /*------------------------------------------------------------------------*/ /** + * Removes `key` and its value from the associative array. + * + * @private + * @param {Array} array The array to modify. + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function assocDelete(array, key) { + var index = assocIndexOf(array, key); + if (index < 0) { + return false; + } + var lastIndex = array.length - 1; + if (index == lastIndex) { + array.pop(); + } else { + splice.call(array, index, 1); + } + return true; + } + + /** + * Gets the associative array value for `key`. + * + * @private + * @param {Array} array The array to query. + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function assocGet(array, key) { + var index = assocIndexOf(array, key); + return index < 0 ? undefined : array[index][1]; + } + + /** + * Checks if an associative array value for `key` exists. + * + * @private + * @param {Array} array The array to query. + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function assocHas(array, key) { + return assocIndexOf(array, key) > -1; + } + + /** + * Gets the index at which the `key` is found in `array` of key-value pairs. + * + * @private + * @param {Array} array The array to search. + * @param {*} key The key to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function assocIndexOf(array, key) { + var length = array.length; + while (length--) { + if (eq(array[length][0], key)) { + return length; + } + } + return -1; + } + + /** + * Sets the associative array `key` to `value`. + * + * @private + * @param {Array} array The array to modify. + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + */ + function assocSet(array, key, value) { + var index = assocIndexOf(array, key); + if (index < 0) { + array.push([key, value]); + } else { + array[index][1] = value; + } + } + + /*------------------------------------------------------------------------*/ + + /** * Used by `_.defaults` to customize its `_.assignIn` use. * * @private @@ -37911,24 +37894,6 @@ return jQuery; } /** - * Gets the index at which the `key` is found in `array` of key-value pairs. - * - * @private - * @param {Array} array The array to search. - * @param {*} key The key to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function assocIndexOf(array, key) { - var length = array.length; - while (length--) { - if (eq(array[length][0], key)) { - return length; - } - } - return -1; - } - - /** * Aggregates elements of `collection` on `accumulator` with keys transformed * by `iteratee` and values set by `setter`. * @@ -37965,7 +37930,7 @@ return jQuery; * @private * @param {Object} object The object to iterate over. * @param {string[]} paths The property paths of elements to pick. - * @returns {Array} Returns the picked elements. + * @returns {Array} Returns the new array of picked elements. */ function baseAt(object, paths) { var index = -1, @@ -38080,7 +38045,7 @@ return jQuery; * * @private * @param {Object} source The object of property predicates to conform to. - * @returns {Function} Returns the new spec function. + * @returns {Function} Returns the new function. */ function baseConforms(source) { var props = keys(source), @@ -38393,7 +38358,7 @@ return jQuery; * @private * @param {Object} object The object to inspect. * @param {Array} props The property names to filter. - * @returns {Array} Returns the function names. + * @returns {Array} Returns the new array of filtered property names. */ function baseFunctions(object, props) { return arrayFilter(props, function(key) { @@ -38434,7 +38399,9 @@ return jQuery; */ function baseGetAllKeys(object, keysFunc, symbolsFunc) { var result = keysFunc(object); - return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); + return isArray(object) + ? result + : arrayPush(result, symbolsFunc(object)); } /** @@ -38826,7 +38793,7 @@ return jQuery; * * @private * @param {Object} source The object of property values to match. - * @returns {Function} Returns the new spec function. + * @returns {Function} Returns the new function. */ function baseMatches(source) { var matchData = getMatchData(source); @@ -38844,7 +38811,7 @@ return jQuery; * @private * @param {string} path The path of the property to get. * @param {*} srcValue The value to match. - * @returns {Function} Returns the new spec function. + * @returns {Function} Returns the new function. */ function baseMatchesProperty(path, srcValue) { if (isKey(path) && isStrictComparable(srcValue)) { @@ -39059,7 +39026,7 @@ return jQuery; * * @private * @param {string} key The key of the property to get. - * @returns {Function} Returns the new accessor function. + * @returns {Function} Returns the new function. */ function baseProperty(key) { return function(object) { @@ -39072,7 +39039,7 @@ return jQuery; * * @private * @param {Array|string} path The path of the property to get. - * @returns {Function} Returns the new accessor function. + * @returns {Function} Returns the new function. */ function basePropertyDeep(path) { return function(object) { @@ -39173,7 +39140,7 @@ return jQuery; * @param {number} end The end of the range. * @param {number} step The value to increment or decrement by. * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Array} Returns the range of numbers. + * @returns {Array} Returns the new array of numbers. */ function baseRange(start, end, step, fromRight) { var index = -1, @@ -39887,7 +39854,7 @@ return jQuery; * placeholders, and provided arguments into a single array of arguments. * * @private - * @param {Array} args The provided arguments. + * @param {Array|Object} args The provided arguments. * @param {Array} partials The arguments to prepend to those provided. * @param {Array} holders The `partials` placeholder indexes. * @params {boolean} [isCurried] Specify composing for a curried function. @@ -39922,7 +39889,7 @@ return jQuery; * is tailored for `_.partialRight`. * * @private - * @param {Array} args The provided arguments. + * @param {Array|Object} args The provided arguments. * @param {Array} partials The arguments to append to those provided. * @param {Array} holders The `partials` placeholder indexes. * @params {boolean} [isCurried] Specify composing for a curried function. @@ -40044,7 +40011,7 @@ return jQuery; customizer = length > 1 ? sources[length - 1] : undefined, guard = length > 2 ? sources[2] : undefined; - customizer = (assigner.length > 3 && typeof customizer == 'function') + customizer = typeof customizer == 'function' ? (length--, customizer) : undefined; @@ -40143,7 +40110,7 @@ return jQuery; * * @private * @param {string} methodName The name of the `String` case method to use. - * @returns {Function} Returns the new case function. + * @returns {Function} Returns the new function. */ function createCaseFirst(methodName) { return function(string) { @@ -40228,7 +40195,7 @@ return jQuery; var length = arguments.length, args = Array(length), index = length, - placeholder = getHolder(wrapper); + placeholder = getPlaceholder(wrapper); while (index--) { args[index] = arguments[index]; @@ -40343,14 +40310,14 @@ return jQuery; function wrapper() { var length = arguments.length, - args = Array(length), - index = length; + index = length, + args = Array(length); while (index--) { args[index] = arguments[index]; } if (isCurried) { - var placeholder = getHolder(wrapper), + var placeholder = getPlaceholder(wrapper), holdersCount = countHolders(args, placeholder); } if (partials) { @@ -40439,7 +40406,7 @@ return jQuery; * * @private * @param {Function} arrayFunc The function to iterate over iteratees. - * @returns {Function} Returns the new over function. + * @returns {Function} Returns the new invoker function. */ function createOver(arrayFunc) { return rest(function(iteratees) { @@ -40638,26 +40605,6 @@ return jQuery; }; /** - * Creates a `_.toPairs` or `_.toPairsIn` function. - * - * @private - * @param {Function} keysFunc The function to get the keys of a given object. - * @returns {Function} Returns the new pairs function. - */ - function createToPairs(keysFunc) { - return function(object) { - var tag = getTag(object); - if (tag == mapTag) { - return mapToArray(object); - } - if (tag == setTag) { - return setToPairs(object); - } - return baseToPairs(object, keysFunc(object)); - }; - } - - /** * Creates a function that either curries or invokes `func` with optional * `this` binding and partially applied arguments. * @@ -40674,7 +40621,6 @@ return jQuery; * 64 - `_.partialRight` * 128 - `_.rearg` * 256 - `_.ary` - * 512 - `_.flip` * @param {*} [thisArg] The `this` binding of `func`. * @param {Array} [partials] The arguments to be partially applied. * @param {Array} [holders] The `partials` placeholder indexes. @@ -40753,7 +40699,9 @@ return jQuery; * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. */ function equalArrays(array, other, equalFunc, customizer, bitmask, stack) { - var isPartial = bitmask & PARTIAL_COMPARE_FLAG, + var index = -1, + isPartial = bitmask & PARTIAL_COMPARE_FLAG, + isUnordered = bitmask & UNORDERED_COMPARE_FLAG, arrLength = array.length, othLength = other.length; @@ -40765,10 +40713,7 @@ return jQuery; if (stacked) { return stacked == other; } - var index = -1, - result = true, - seen = (bitmask & UNORDERED_COMPARE_FLAG) ? new SetCache : undefined; - + var result = true; stack.set(array, other); // Ignore non-index properties. @@ -40789,12 +40734,10 @@ return jQuery; break; } // Recursively compare arrays (susceptible to call stack limits). - if (seen) { - if (!arraySome(other, function(othValue, othIndex) { - if (!seen.has(othIndex) && - (arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack))) { - return seen.add(othIndex); - } + if (isUnordered) { + if (!arraySome(other, function(othValue) { + return arrValue === othValue || + equalFunc(arrValue, othValue, customizer, bitmask, stack); })) { result = false; break; @@ -41029,18 +40972,6 @@ return jQuery; } /** - * Gets the argument placeholder value for `func`. - * - * @private - * @param {Function} func The function to inspect. - * @returns {*} Returns the placeholder value. - */ - function getHolder(func) { - var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func; - return object.placeholder; - } - - /** * Gets the appropriate "iteratee" function. If `_.iteratee` is customized, * this function returns the custom method, otherwise it returns `baseIteratee`. * If arguments are provided, the chosen function is invoked with them and @@ -41071,21 +41002,6 @@ return jQuery; var getLength = baseProperty('length'); /** - * Gets the data for `map`. - * - * @private - * @param {Object} map The map to query. - * @param {string} key The reference key. - * @returns {*} Returns the map data. - */ - function getMapData(map, key) { - var data = map.__data__; - return isKeyable(key) - ? data[typeof key == 'string' ? 'string' : 'hash'] - : data.map; - } - - /** * Gets the property names, values, and compare flags of `object`. * * @private @@ -41116,6 +41032,18 @@ return jQuery; } /** + * Gets the argument placeholder value for `func`. + * + * @private + * @param {Function} func The function to inspect. + * @returns {*} Returns the placeholder value. + */ + function getPlaceholder(func) { + var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func; + return object.placeholder; + } + + /** * Gets the `[[Prototype]]` of `value`. * * @private @@ -41364,7 +41292,7 @@ return jQuery; * @returns {boolean} Returns `true` if `value` is flattenable, else `false`. */ function isFlattenable(value) { - return isArray(value) || isArguments(value); + return isArrayLikeObject(value) && (isArray(value) || isArguments(value)); } /** @@ -41508,7 +41436,7 @@ return jQuery; * @private * @param {string} key The key of the property to get. * @param {*} srcValue The value to match. - * @returns {Function} Returns the new spec function. + * @returns {Function} Returns the new function. */ function matchesStrictComparable(key, srcValue) { return function(object) { @@ -41760,7 +41688,7 @@ return jQuery; * @param {Array} array The array to process. * @param {number} [size=1] The length of each chunk * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the new array of chunks. + * @returns {Array} Returns the new array containing chunks. * @example * * _.chunk(['a', 'b', 'c', 'd'], 2); @@ -41843,16 +41771,16 @@ return jQuery; */ function concat() { var length = arguments.length, - args = Array(length ? length - 1 : 0), - array = arguments[0], - index = length; + array = castArray(arguments[0]); - while (index--) { - args[index - 1] = arguments[index]; + if (length < 2) { + return length ? copyArray(array) : []; } - return length - ? arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)) - : []; + var args = Array(length - 1); + while (length--) { + args[length - 1] = arguments[length]; + } + return arrayConcat(array, baseFlatten(args, 1)); } /** @@ -42571,8 +42499,8 @@ return jQuery; } /** - * Gets the element at `n` index of `array`. If `n` is negative, the nth - * element from the end is returned. + * Gets the nth element of `array`. If `n` is negative, the nth element + * from the end is returned. * * @static * @memberOf _ @@ -43452,7 +43380,7 @@ return jQuery; * @memberOf _ * @since 0.1.0 * @category Array - * @param {Array} array The array to inspect. + * @param {Array} array The array to filter. * @param {...*} [values] The values to exclude. * @returns {Array} Returns the new array of filtered values. * @see _.difference, _.xor @@ -43478,7 +43406,7 @@ return jQuery; * @since 2.4.0 * @category Array * @param {...Array} [arrays] The arrays to inspect. - * @returns {Array} Returns the new array of filtered values. + * @returns {Array} Returns the new array of values. * @see _.difference, _.without * @example * @@ -43502,7 +43430,7 @@ return jQuery; * @param {...Array} [arrays] The arrays to inspect. * @param {Array|Function|Object|string} [iteratee=_.identity] * The iteratee invoked per element. - * @returns {Array} Returns the new array of filtered values. + * @returns {Array} Returns the new array of values. * @example * * _.xorBy([2.1, 1.2], [4.3, 2.4], Math.floor); @@ -43531,7 +43459,7 @@ return jQuery; * @category Array * @param {...Array} [arrays] The arrays to inspect. * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of filtered values. + * @returns {Array} Returns the new array of values. * @example * * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; @@ -44279,8 +44207,9 @@ return jQuery; * // => Logs 'a' then 'b' (iteration order is not guaranteed). */ function forEach(collection, iteratee) { - var func = isArray(collection) ? arrayEach : baseEach; - return func(collection, getIteratee(iteratee, 3)); + return (typeof iteratee == 'function' && isArray(collection)) + ? arrayEach(collection, iteratee) + : baseEach(collection, getIteratee(iteratee)); } /** @@ -44304,8 +44233,9 @@ return jQuery; * // => Logs `2` then `1`. */ function forEachRight(collection, iteratee) { - var func = isArray(collection) ? arrayEachRight : baseEachRight; - return func(collection, getIteratee(iteratee, 3)); + return (typeof iteratee == 'function' && isArray(collection)) + ? arrayEachRight(collection, iteratee) + : baseEachRight(collection, getIteratee(iteratee)); } /** @@ -44986,7 +44916,7 @@ return jQuery; * @param {Function} func The function to cap arguments for. * @param {number} [n=func.length] The arity cap. * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the new capped function. + * @returns {Function} Returns the new function. * @example * * _.map(['6', '8', '10'], _.ary(parseInt, 1)); @@ -45070,7 +45000,7 @@ return jQuery; var bind = rest(function(func, thisArg, partials) { var bitmask = BIND_FLAG; if (partials.length) { - var holders = replaceHolders(partials, getHolder(bind)); + var holders = replaceHolders(partials, getPlaceholder(bind)); bitmask |= PARTIAL_FLAG; } return createWrapper(func, bitmask, thisArg, partials, holders); @@ -45124,7 +45054,7 @@ return jQuery; var bindKey = rest(function(object, key, partials) { var bitmask = BIND_FLAG | BIND_KEY_FLAG; if (partials.length) { - var holders = replaceHolders(partials, getHolder(bindKey)); + var holders = replaceHolders(partials, getPlaceholder(bindKey)); bitmask |= PARTIAL_FLAG; } return createWrapper(key, bitmask, object, partials, holders); @@ -45450,7 +45380,7 @@ return jQuery; * @since 4.0.0 * @category Function * @param {Function} func The function to flip arguments for. - * @returns {Function} Returns the new flipped function. + * @returns {Function} Returns the new function. * @example * * var flipped = _.flip(function() { @@ -45483,7 +45413,7 @@ return jQuery; * @category Function * @param {Function} func The function to have its output memoized. * @param {Function} [resolver] The function to resolve the cache key. - * @returns {Function} Returns the new memoized function. + * @returns {Function} Returns the new memoizing function. * @example * * var object = { 'a': 1, 'b': 2 }; @@ -45541,7 +45471,7 @@ return jQuery; * @since 3.0.0 * @category Function * @param {Function} predicate The predicate to negate. - * @returns {Function} Returns the new negated function. + * @returns {Function} Returns the new function. * @example * * function isEven(n) { @@ -45665,7 +45595,7 @@ return jQuery; * // => 'hi fred' */ var partial = rest(function(func, partials) { - var holders = replaceHolders(partials, getHolder(partial)); + var holders = replaceHolders(partials, getPlaceholder(partial)); return createWrapper(func, PARTIAL_FLAG, undefined, partials, holders); }); @@ -45702,7 +45632,7 @@ return jQuery; * // => 'hello fred' */ var partialRight = rest(function(func, partials) { - var holders = replaceHolders(partials, getHolder(partialRight)); + var holders = replaceHolders(partials, getPlaceholder(partialRight)); return createWrapper(func, PARTIAL_RIGHT_FLAG, undefined, partials, holders); }); @@ -45904,7 +45834,7 @@ return jQuery; * @since 4.0.0 * @category Function * @param {Function} func The function to cap arguments for. - * @returns {Function} Returns the new capped function. + * @returns {Function} Returns the new function. * @example * * _.map(['6', '8', '10'], _.unary(parseInt)); @@ -46580,13 +46510,13 @@ return jQuery; * _.isFinite(3); * // => true * - * _.isFinite(Number.MIN_VALUE); + * _.isFinite(Number.MAX_VALUE); * // => true * - * _.isFinite(Infinity); - * // => false + * _.isFinite(3.14); + * // => true * - * _.isFinite('3'); + * _.isFinite(Infinity); * // => false */ function isFinite(value) { @@ -47309,41 +47239,6 @@ return jQuery; } /** - * Converts `value` to a finite number. - * - * @static - * @memberOf _ - * @since 4.12.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted number. - * @example - * - * _.toFinite(3.2); - * // => 3.2 - * - * _.toFinite(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toFinite(Infinity); - * // => 1.7976931348623157e+308 - * - * _.toFinite('3.2'); - * // => 3.2 - */ - function toFinite(value) { - if (!value) { - return value === 0 ? value : 0; - } - value = toNumber(value); - if (value === INFINITY || value === -INFINITY) { - var sign = (value < 0 ? -1 : 1); - return sign * MAX_INTEGER; - } - return value === value ? value : 0; - } - - /** * Converts `value` to an integer. * * **Note:** This function is loosely based on @@ -47357,7 +47252,7 @@ return jQuery; * @returns {number} Returns the converted integer. * @example * - * _.toInteger(3.2); + * _.toInteger(3); * // => 3 * * _.toInteger(Number.MIN_VALUE); @@ -47366,14 +47261,20 @@ return jQuery; * _.toInteger(Infinity); * // => 1.7976931348623157e+308 * - * _.toInteger('3.2'); + * _.toInteger('3'); * // => 3 */ function toInteger(value) { - var result = toFinite(value), - remainder = result % 1; - - return result === result ? (remainder ? result - remainder : result) : 0; + if (!value) { + return value === 0 ? value : 0; + } + value = toNumber(value); + if (value === INFINITY || value === -INFINITY) { + var sign = (value < 0 ? -1 : 1); + return sign * MAX_INTEGER; + } + var remainder = value % 1; + return value === value ? (remainder ? value - remainder : value) : 0; } /** @@ -47391,7 +47292,7 @@ return jQuery; * @returns {number} Returns the converted integer. * @example * - * _.toLength(3.2); + * _.toLength(3); * // => 3 * * _.toLength(Number.MIN_VALUE); @@ -47400,7 +47301,7 @@ return jQuery; * _.toLength(Infinity); * // => 4294967295 * - * _.toLength('3.2'); + * _.toLength('3'); * // => 3 */ function toLength(value) { @@ -47418,8 +47319,8 @@ return jQuery; * @returns {number} Returns the number. * @example * - * _.toNumber(3.2); - * // => 3.2 + * _.toNumber(3); + * // => 3 * * _.toNumber(Number.MIN_VALUE); * // => 5e-324 @@ -47427,8 +47328,8 @@ return jQuery; * _.toNumber(Infinity); * // => Infinity * - * _.toNumber('3.2'); - * // => 3.2 + * _.toNumber('3'); + * // => 3 */ function toNumber(value) { if (typeof value == 'number') { @@ -47491,7 +47392,7 @@ return jQuery; * @returns {number} Returns the converted integer. * @example * - * _.toSafeInteger(3.2); + * _.toSafeInteger(3); * // => 3 * * _.toSafeInteger(Number.MIN_VALUE); @@ -47500,7 +47401,7 @@ return jQuery; * _.toSafeInteger(Infinity); * // => 9007199254740991 * - * _.toSafeInteger('3.2'); + * _.toSafeInteger('3'); * // => 3 */ function toSafeInteger(value) { @@ -47693,7 +47594,7 @@ return jQuery; * @category Object * @param {Object} object The object to iterate over. * @param {...(string|string[])} [paths] The property paths of elements to pick. - * @returns {Array} Returns the picked values. + * @returns {Array} Returns the new array of picked elements. * @example * * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; @@ -47909,7 +47810,7 @@ return jQuery; function forIn(object, iteratee) { return object == null ? object - : baseFor(object, getIteratee(iteratee, 3), keysIn); + : baseFor(object, getIteratee(iteratee), keysIn); } /** @@ -47941,7 +47842,7 @@ return jQuery; function forInRight(object, iteratee) { return object == null ? object - : baseForRight(object, getIteratee(iteratee, 3), keysIn); + : baseForRight(object, getIteratee(iteratee), keysIn); } /** @@ -47973,7 +47874,7 @@ return jQuery; * // => Logs 'a' then 'b' (iteration order is not guaranteed). */ function forOwn(object, iteratee) { - return object && baseForOwn(object, getIteratee(iteratee, 3)); + return object && baseForOwn(object, getIteratee(iteratee)); } /** @@ -48003,7 +47904,7 @@ return jQuery; * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'. */ function forOwnRight(object, iteratee) { - return object && baseForOwnRight(object, getIteratee(iteratee, 3)); + return object && baseForOwnRight(object, getIteratee(iteratee)); } /** @@ -48015,7 +47916,7 @@ return jQuery; * @memberOf _ * @category Object * @param {Object} object The object to inspect. - * @returns {Array} Returns the function names. + * @returns {Array} Returns the new array of property names. * @see _.functionsIn * @example * @@ -48042,7 +47943,7 @@ return jQuery; * @since 4.0.0 * @category Object * @param {Object} object The object to inspect. - * @returns {Array} Returns the function names. + * @returns {Array} Returns the new array of property names. * @see _.functions * @example * @@ -48395,7 +48296,7 @@ return jQuery; * inherited enumerable string keyed properties of source objects into the * destination object. Source properties that resolve to `undefined` are * skipped if a destination value exists. Array and plain object properties - * are merged recursively. Other objects and value types are overridden by + * are merged recursively.Other objects and value types are overridden by * assignment. Source objects are applied from left to right. Subsequent * sources overwrite property assignments of previous sources. * @@ -48680,8 +48581,7 @@ return jQuery; /** * Creates an array of own enumerable string keyed-value pairs for `object` - * which can be consumed by `_.fromPairs`. If `object` is a map or set, its - * entries are returned. + * which can be consumed by `_.fromPairs`. * * @static * @memberOf _ @@ -48689,7 +48589,7 @@ return jQuery; * @alias entries * @category Object * @param {Object} object The object to query. - * @returns {Array} Returns the key-value pairs. + * @returns {Array} Returns the new array of key-value pairs. * @example * * function Foo() { @@ -48702,12 +48602,13 @@ return jQuery; * _.toPairs(new Foo); * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed) */ - var toPairs = createToPairs(keys); + function toPairs(object) { + return baseToPairs(object, keys(object)); + } /** * Creates an array of own and inherited enumerable string keyed-value pairs - * for `object` which can be consumed by `_.fromPairs`. If `object` is a map - * or set, its entries are returned. + * for `object` which can be consumed by `_.fromPairs`. * * @static * @memberOf _ @@ -48715,7 +48616,7 @@ return jQuery; * @alias entriesIn * @category Object * @param {Object} object The object to query. - * @returns {Array} Returns the key-value pairs. + * @returns {Array} Returns the new array of key-value pairs. * @example * * function Foo() { @@ -48726,9 +48627,11 @@ return jQuery; * Foo.prototype.c = 3; * * _.toPairsIn(new Foo); - * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed) + * // => [['a', 1], ['b', 2], ['c', 1]] (iteration order is not guaranteed) */ - var toPairsIn = createToPairs(keysIn); + function toPairsIn(object) { + return baseToPairs(object, keysIn(object)); + } /** * An alternative to `_.reduce`; this method transforms `object` to a new @@ -49558,7 +49461,7 @@ return jQuery; * @param {string} [string=''] The string to split. * @param {RegExp|string} separator The separator pattern to split by. * @param {number} [limit] The length to truncate results to. - * @returns {Array} Returns the string segments. + * @returns {Array} Returns the new array of string segments. * @example * * _.split('a-b-c', '-', 2); @@ -49703,6 +49606,12 @@ return jQuery; * compiled({ 'user': 'pebbles' }); * // => 'hello pebbles!' * + * // Use custom template delimiters. + * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; + * var compiled = _.template('hello {{ user }}!'); + * compiled({ 'user': 'mustache' }); + * // => 'hello mustache!' + * * // Use backslashes to treat delimiters as plain text. * var compiled = _.template('<%= "\\<%- value %\\>" %>'); * compiled({ 'value': 'ignored' }); @@ -49728,15 +49637,9 @@ return jQuery; * // return __p; * // } * - * // Use custom template delimiters. - * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; - * var compiled = _.template('hello {{ user }}!'); - * compiled({ 'user': 'mustache' }); - * // => 'hello mustache!' - * * // Use the `source` property to inline compiled templates for meaningful * // line numbers in error messages and stack traces. - * fs.writeFileSync(path.join(process.cwd(), 'jst.js'), '\ + * fs.writeFileSync(path.join(cwd, 'jst.js'), '\ * var JST = {\ * "main": ' + _.template(mainText).source + '\ * };\ @@ -50272,7 +50175,7 @@ return jQuery; * @since 4.0.0 * @category Util * @param {Array} pairs The predicate-function pairs. - * @returns {Function} Returns the new composite function. + * @returns {Function} Returns the new function. * @example * * var func = _.cond([ @@ -50322,7 +50225,7 @@ return jQuery; * @since 4.0.0 * @category Util * @param {Object} source The object of property predicates to conform to. - * @returns {Function} Returns the new spec function. + * @returns {Function} Returns the new function. * @example * * var users = [ @@ -50345,7 +50248,7 @@ return jQuery; * @since 2.4.0 * @category Util * @param {*} value The value to return from the new function. - * @returns {Function} Returns the new constant function. + * @returns {Function} Returns the new function. * @example * * var object = { 'user': 'fred' }; @@ -50370,7 +50273,7 @@ return jQuery; * @since 3.0.0 * @category Util * @param {...(Function|Function[])} [funcs] Functions to invoke. - * @returns {Function} Returns the new composite function. + * @returns {Function} Returns the new function. * @see _.flowRight * @example * @@ -50393,7 +50296,7 @@ return jQuery; * @memberOf _ * @category Util * @param {...(Function|Function[])} [funcs] Functions to invoke. - * @returns {Function} Returns the new composite function. + * @returns {Function} Returns the new function. * @see _.flow * @example * @@ -50486,7 +50389,7 @@ return jQuery; * @since 3.0.0 * @category Util * @param {Object} source The object of property values to match. - * @returns {Function} Returns the new spec function. + * @returns {Function} Returns the new function. * @example * * var users = [ @@ -50514,7 +50417,7 @@ return jQuery; * @category Util * @param {Array|string} path The path of the property to get. * @param {*} srcValue The value to match. - * @returns {Function} Returns the new spec function. + * @returns {Function} Returns the new function. * @example * * var users = [ @@ -50539,7 +50442,7 @@ return jQuery; * @category Util * @param {Array|string} path The path of the method to invoke. * @param {...*} [args] The arguments to invoke the method with. - * @returns {Function} Returns the new invoker function. + * @returns {Function} Returns the new function. * @example * * var objects = [ @@ -50570,7 +50473,7 @@ return jQuery; * @category Util * @param {Object} object The object to query. * @param {...*} [args] The arguments to invoke the method with. - * @returns {Function} Returns the new invoker function. + * @returns {Function} Returns the new function. * @example * * var array = _.times(3, _.constant), @@ -50700,7 +50603,7 @@ return jQuery; } /** - * Creates a function that gets the argument at `n` index. If `n` is negative, + * Creates a function that returns its nth argument. If `n` is negative, * the nth argument from the end is returned. * * @static @@ -50708,7 +50611,7 @@ return jQuery; * @since 4.0.0 * @category Util * @param {number} [n=0] The index of the argument to return. - * @returns {Function} Returns the new pass-thru function. + * @returns {Function} Returns the new function. * @example * * var func = _.nthArg(1); @@ -50806,7 +50709,7 @@ return jQuery; * @since 2.4.0 * @category Util * @param {Array|string} path The path of the property to get. - * @returns {Function} Returns the new accessor function. + * @returns {Function} Returns the new function. * @example * * var objects = [ @@ -50833,7 +50736,7 @@ return jQuery; * @since 3.0.0 * @category Util * @param {Object} object The object to query. - * @returns {Function} Returns the new accessor function. + * @returns {Function} Returns the new function. * @example * * var array = [0, 1, 2], @@ -50867,7 +50770,7 @@ return jQuery; * @param {number} [start=0] The start of the range. * @param {number} end The end of the range. * @param {number} [step=1] The value to increment or decrement by. - * @returns {Array} Returns the range of numbers. + * @returns {Array} Returns the new array of numbers. * @see _.inRange, _.rangeRight * @example * @@ -50905,7 +50808,7 @@ return jQuery; * @param {number} [start=0] The start of the range. * @param {number} end The end of the range. * @param {number} [step=1] The value to increment or decrement by. - * @returns {Array} Returns the range of numbers. + * @returns {Array} Returns the new array of numbers. * @see _.inRange, _.range * @example * @@ -51666,7 +51569,6 @@ return jQuery; lodash.sumBy = sumBy; lodash.template = template; lodash.times = times; - lodash.toFinite = toFinite; lodash.toInteger = toInteger; lodash.toLength = toLength; lodash.toLower = toLower; diff --git a/mitmproxy/webfonts/fontawesome-webfont.eot b/mitmproxy/webfonts/fontawesome-webfont.eot Binary files differdeleted file mode 100644 index 84677bc0..00000000 --- a/mitmproxy/webfonts/fontawesome-webfont.eot +++ /dev/null diff --git a/mitmproxy/webfonts/fontawesome-webfont.svg b/mitmproxy/webfonts/fontawesome-webfont.svg deleted file mode 100644 index d907b25a..00000000 --- a/mitmproxy/webfonts/fontawesome-webfont.svg +++ /dev/null @@ -1,520 +0,0 @@ -<?xml version="1.0" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > -<svg xmlns="http://www.w3.org/2000/svg"> -<metadata></metadata> -<defs> -<font id="fontawesomeregular" horiz-adv-x="1536" > -<font-face units-per-em="1792" ascent="1536" descent="-256" /> -<missing-glyph horiz-adv-x="448" /> -<glyph unicode=" " horiz-adv-x="448" /> -<glyph unicode="	" horiz-adv-x="448" /> -<glyph unicode=" " horiz-adv-x="448" /> -<glyph unicode="¨" horiz-adv-x="1792" /> -<glyph unicode="©" horiz-adv-x="1792" /> -<glyph unicode="®" horiz-adv-x="1792" /> -<glyph unicode="´" horiz-adv-x="1792" /> -<glyph unicode="Æ" horiz-adv-x="1792" /> -<glyph unicode="Ø" horiz-adv-x="1792" /> -<glyph unicode=" " horiz-adv-x="768" /> -<glyph unicode=" " horiz-adv-x="1537" /> -<glyph unicode=" " horiz-adv-x="768" /> -<glyph unicode=" " horiz-adv-x="1537" /> -<glyph unicode=" " horiz-adv-x="512" /> -<glyph unicode=" " horiz-adv-x="384" /> -<glyph unicode=" " horiz-adv-x="256" /> -<glyph unicode=" " horiz-adv-x="256" /> -<glyph unicode=" " horiz-adv-x="192" /> -<glyph unicode=" " horiz-adv-x="307" /> -<glyph unicode=" " horiz-adv-x="85" /> -<glyph unicode=" " horiz-adv-x="307" /> -<glyph unicode=" " horiz-adv-x="384" /> -<glyph unicode="™" horiz-adv-x="1792" /> -<glyph unicode="∞" horiz-adv-x="1792" /> -<glyph unicode="≠" horiz-adv-x="1792" /> -<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" /> -<glyph unicode="" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5 t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1664 32v768q-32 -36 -69 -66q-268 -206 -426 -338q-51 -43 -83 -67t-86.5 -48.5t-102.5 -24.5h-1h-1q-48 0 -102.5 24.5t-86.5 48.5t-83 67q-158 132 -426 338q-37 30 -69 66v-768q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1664 1083v11v13.5t-0.5 13 t-3 12.5t-5.5 9t-9 7.5t-14 2.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5q0 -168 147 -284q193 -152 401 -317q6 -5 35 -29.5t46 -37.5t44.5 -31.5t50.5 -27.5t43 -9h1h1q20 0 43 9t50.5 27.5t44.5 31.5t46 37.5t35 29.5q208 165 401 317q54 43 100.5 115.5t46.5 131.5z M1792 1120v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113z" /> -<glyph unicode="" horiz-adv-x="1792" d="M896 -128q-26 0 -44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600 q-18 -18 -44 -18z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -21 -10.5 -35.5t-30.5 -14.5q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455 l502 -73q56 -9 56 -46z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1137 532l306 297l-422 62l-189 382l-189 -382l-422 -62l306 -297l-73 -421l378 199l377 -199zM1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -50 -41 -50q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500 l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1408 131q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q9 0 42 -21.5t74.5 -48t108 -48t133.5 -21.5t133.5 21.5t108 48t74.5 48t42 21.5q61 0 111.5 -20t85.5 -53.5t62 -81 t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" /> -<glyph unicode="" horiz-adv-x="1920" d="M384 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 320v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 704v128q0 26 -19 45t-45 19h-128 q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 -64v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM384 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45 t45 -19h128q26 0 45 19t19 45zM1792 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 704v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1792 320v128 q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 704v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19 t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1920 1248v-1344q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1344q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" /> -<glyph unicode="" horiz-adv-x="1664" d="M768 512v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM768 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 512v-384q0 -52 -38 -90t-90 -38 h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" /> -<glyph unicode="" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 288v-192q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68z" /> -<glyph unicode="" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h960q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1671 970q0 -40 -28 -68l-724 -724l-136 -136q-28 -28 -68 -28t-68 28l-136 136l-362 362q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -295l656 657q28 28 68 28t68 -28l136 -136q28 -28 28 -68z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1298 214q0 -40 -28 -68l-136 -136q-28 -28 -68 -28t-68 28l-294 294l-294 -294q-28 -28 -68 -28t-68 28l-136 136q-28 28 -28 68t28 68l294 294l-294 294q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -294l294 294q28 28 68 28t68 -28l136 -136q28 -28 28 -68 t-28 -68l-294 -294l294 -294q28 -28 28 -68z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-224q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v224h-224q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h224v224q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-224h224 q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5 t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-576q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h576q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5z M1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z " /> -<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61t-298 61t-245 164t-164 245t-61 298q0 182 80.5 343t226.5 270q43 32 95.5 25t83.5 -50q32 -42 24.5 -94.5t-49.5 -84.5q-98 -74 -151.5 -181t-53.5 -228q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5 t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5q0 121 -53.5 228t-151.5 181q-42 32 -49.5 84.5t24.5 94.5q31 43 84 50t95 -25q146 -109 226.5 -270t80.5 -343zM896 1408v-640q0 -52 -38 -90t-90 -38t-90 38t-38 90v640q0 52 38 90t90 38t90 -38t38 -90z" /> -<glyph unicode="" horiz-adv-x="1792" d="M256 96v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 224v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 480v-576q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1408 864v-960q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1376v-1472q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1472q0 14 9 23t23 9h192q14 0 23 -9t9 -23z" /> -<glyph unicode="" d="M1024 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1536 749v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108q-44 -23 -91 -38 q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5v222q0 12 8 23t19 13 l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10q129 -119 165 -170q7 -8 7 -22 q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M512 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM768 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1024 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1152 76v948h-896v-948q0 -22 7 -40.5t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832 q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1408 544v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6zM1631 613l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5t11 21.5 l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5z" /> -<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z " /> -<glyph unicode="" d="M896 992v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1111 540v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20zM1870 73q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256 q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1280 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 416v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h465l135 -136 q58 -56 136 -56t136 56l136 136h464q40 0 68 -28t28 -68zM1339 985q17 -41 -14 -70l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39z" /> -<glyph unicode="" d="M1120 608q0 -12 -10 -24l-319 -319q-11 -9 -23 -9t-23 9l-320 320q-15 16 -7 35q8 20 30 20h192v352q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-352h192q14 0 23 -9t9 -23zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273 t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1118 660q-8 -20 -30 -20h-192v-352q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v352h-192q-14 0 -23 9t-9 23q0 12 10 24l319 319q11 9 23 9t23 -9l320 -320q15 -16 7 -35zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198 t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1023 576h316q-1 3 -2.5 8t-2.5 8l-212 496h-708l-212 -496q-1 -2 -2.5 -8t-2.5 -8h316l95 -192h320zM1536 546v-482q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v482q0 62 25 123l238 552q10 25 36.5 42t52.5 17h832q26 0 52.5 -17t36.5 -42l238 -552 q25 -61 25 -123z" /> -<glyph unicode="" d="M1184 640q0 -37 -32 -55l-544 -320q-15 -9 -32 -9q-16 0 -32 8q-32 19 -32 56v640q0 37 32 56q33 18 64 -1l544 -320q32 -18 32 -55zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l138 138q-148 137 -349 137q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5q119 0 225 52t179 147q7 10 23 12q14 0 25 -9 l137 -138q9 -8 9.5 -20.5t-7.5 -22.5q-109 -132 -264 -204.5t-327 -72.5q-156 0 -298 61t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q147 0 284.5 -55.5t244.5 -156.5l130 129q29 31 70 14q39 -17 39 -59z" /> -<glyph unicode="" d="M1511 480q0 -5 -1 -7q-64 -268 -268 -434.5t-478 -166.5q-146 0 -282.5 55t-243.5 157l-129 -129q-19 -19 -45 -19t-45 19t-19 45v448q0 26 19 45t45 19h448q26 0 45 -19t19 -45t-19 -45l-137 -137q71 -66 161 -102t187 -36q134 0 250 65t186 179q11 17 53 117 q8 23 30 23h192q13 0 22.5 -9.5t9.5 -22.5zM1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-26 0 -45 19t-19 45t19 45l138 138q-148 137 -349 137q-134 0 -250 -65t-186 -179q-11 -17 -53 -117q-8 -23 -30 -23h-199q-13 0 -22.5 9.5t-9.5 22.5v7q65 268 270 434.5t480 166.5 q146 0 284 -55.5t245 -156.5l130 129q19 19 45 19t45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M384 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M384 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1536 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5z M1536 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5zM1536 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5 t9.5 -22.5zM1664 160v832q0 13 -9.5 22.5t-22.5 9.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1792 1248v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47 t47 -113z" /> -<glyph unicode="" horiz-adv-x="1152" d="M320 768h512v192q0 106 -75 181t-181 75t-181 -75t-75 -181v-192zM1152 672v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v192q0 184 132 316t316 132t316 -132t132 -316v-192h32q40 0 68 -28t28 -68z" /> -<glyph unicode="" horiz-adv-x="1792" d="M320 1280q0 -72 -64 -110v-1266q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v1266q-64 38 -64 110q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -25 -12.5 -38.5t-39.5 -27.5q-215 -116 -369 -116q-61 0 -123.5 22t-108.5 48 t-115.5 48t-142.5 22q-192 0 -464 -146q-17 -9 -33 -9q-26 0 -45 19t-19 45v742q0 32 31 55q21 14 79 43q236 120 421 120q107 0 200 -29t219 -88q38 -19 88 -19q54 0 117.5 21t110 47t88 47t54.5 21q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1664 650q0 -166 -60 -314l-20 -49l-185 -33q-22 -83 -90.5 -136.5t-156.5 -53.5v-32q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-32q71 0 130 -35.5t93 -95.5l68 12q29 95 29 193q0 148 -88 279t-236.5 209t-315.5 78 t-315.5 -78t-236.5 -209t-88 -279q0 -98 29 -193l68 -12q34 60 93 95.5t130 35.5v32q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v32q-88 0 -156.5 53.5t-90.5 136.5l-185 33l-20 49q-60 148 -60 314q0 151 67 291t179 242.5 t266 163.5t320 61t320 -61t266 -163.5t179 -242.5t67 -291z" /> -<glyph unicode="" horiz-adv-x="768" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1152" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142z" /> -<glyph unicode="" horiz-adv-x="1664" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142zM1408 640q0 -153 -85 -282.5t-225 -188.5q-13 -5 -25 -5q-27 0 -46 19t-19 45q0 39 39 59q56 29 76 44q74 54 115.5 135.5t41.5 173.5t-41.5 173.5 t-115.5 135.5q-20 15 -76 44q-39 20 -39 59q0 26 19 45t45 19q13 0 26 -5q140 -59 225 -188.5t85 -282.5zM1664 640q0 -230 -127 -422.5t-338 -283.5q-13 -5 -26 -5q-26 0 -45 19t-19 45q0 36 39 59q7 4 22.5 10.5t22.5 10.5q46 25 82 51q123 91 192 227t69 289t-69 289 t-192 227q-36 26 -82 51q-7 4 -22.5 10.5t-22.5 10.5q-39 23 -39 59q0 26 19 45t45 19q13 0 26 -5q211 -91 338 -283.5t127 -422.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M384 384v-128h-128v128h128zM384 1152v-128h-128v128h128zM1152 1152v-128h-128v128h128zM128 129h384v383h-384v-383zM128 896h384v384h-384v-384zM896 896h384v384h-384v-384zM640 640v-640h-640v640h640zM1152 128v-128h-128v128h128zM1408 128v-128h-128v128h128z M1408 640v-384h-384v128h-128v-384h-128v640h384v-128h128v128h128zM640 1408v-640h-640v640h640zM1408 1408v-640h-640v640h640z" /> -<glyph unicode="" horiz-adv-x="1792" d="M63 0h-63v1408h63v-1408zM126 1h-32v1407h32v-1407zM220 1h-31v1407h31v-1407zM377 1h-31v1407h31v-1407zM534 1h-62v1407h62v-1407zM660 1h-31v1407h31v-1407zM723 1h-31v1407h31v-1407zM786 1h-31v1407h31v-1407zM943 1h-63v1407h63v-1407zM1100 1h-63v1407h63v-1407z M1226 1h-63v1407h63v-1407zM1352 1h-63v1407h63v-1407zM1446 1h-63v1407h63v-1407zM1635 1h-94v1407h94v-1407zM1698 1h-32v1407h32v-1407zM1792 0h-63v1408h63v-1408z" /> -<glyph unicode="" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91z" /> -<glyph unicode="" horiz-adv-x="1920" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91zM1899 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-36 0 -59 14t-53 45l470 470q37 37 37 90q0 52 -37 91l-715 714q-38 38 -102 64.5t-117 26.5h224q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1639 1058q40 -57 18 -129l-275 -906q-19 -64 -76.5 -107.5t-122.5 -43.5h-923q-77 0 -148.5 53.5t-99.5 131.5q-24 67 -2 127q0 4 3 27t4 37q1 8 -3 21.5t-3 19.5q2 11 8 21t16.5 23.5t16.5 23.5q23 38 45 91.5t30 91.5q3 10 0.5 30t-0.5 28q3 11 17 28t17 23 q21 36 42 92t25 90q1 9 -2.5 32t0.5 28q4 13 22 30.5t22 22.5q19 26 42.5 84.5t27.5 96.5q1 8 -3 25.5t-2 26.5q2 8 9 18t18 23t17 21q8 12 16.5 30.5t15 35t16 36t19.5 32t26.5 23.5t36 11.5t47.5 -5.5l-1 -3q38 9 51 9h761q74 0 114 -56t18 -130l-274 -906 q-36 -119 -71.5 -153.5t-128.5 -34.5h-869q-27 0 -38 -15q-11 -16 -1 -43q24 -70 144 -70h923q29 0 56 15.5t35 41.5l300 987q7 22 5 57q38 -15 59 -43zM575 1056q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5 t-16.5 -22.5zM492 800q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048z" /> -<glyph unicode="" horiz-adv-x="1664" d="M384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1536 576q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 576v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68 v160h-224q-13 0 -22.5 9.5t-9.5 22.5v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5z" /> -<glyph unicode="" horiz-adv-x="1920" d="M960 864q119 0 203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5t84.5 203.5t203.5 84.5zM1664 1280q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181v896q0 106 75 181t181 75h224l51 136 q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224zM960 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M725 977l-170 -450q33 0 136.5 -2t160.5 -2q19 0 57 2q-87 253 -184 452zM0 -128l2 79q23 7 56 12.5t57 10.5t49.5 14.5t44.5 29t31 50.5l237 616l280 724h75h53q8 -14 11 -21l205 -480q33 -78 106 -257.5t114 -274.5q15 -34 58 -144.5t72 -168.5q20 -45 35 -57 q19 -15 88 -29.5t84 -20.5q6 -38 6 -57q0 -4 -0.5 -13t-0.5 -13q-63 0 -190 8t-191 8q-76 0 -215 -7t-178 -8q0 43 4 78l131 28q1 0 12.5 2.5t15.5 3.5t14.5 4.5t15 6.5t11 8t9 11t2.5 14q0 16 -31 96.5t-72 177.5t-42 100l-450 2q-26 -58 -76.5 -195.5t-50.5 -162.5 q0 -22 14 -37.5t43.5 -24.5t48.5 -13.5t57 -8.5t41 -4q1 -19 1 -58q0 -9 -2 -27q-58 0 -174.5 10t-174.5 10q-8 0 -26.5 -4t-21.5 -4q-80 -14 -188 -14z" /> -<glyph unicode="" horiz-adv-x="1408" d="M555 15q74 -32 140 -32q376 0 376 335q0 114 -41 180q-27 44 -61.5 74t-67.5 46.5t-80.5 25t-84 10.5t-94.5 2q-73 0 -101 -10q0 -53 -0.5 -159t-0.5 -158q0 -8 -1 -67.5t-0.5 -96.5t4.5 -83.5t12 -66.5zM541 761q42 -7 109 -7q82 0 143 13t110 44.5t74.5 89.5t25.5 142 q0 70 -29 122.5t-79 82t-108 43.5t-124 14q-50 0 -130 -13q0 -50 4 -151t4 -152q0 -27 -0.5 -80t-0.5 -79q0 -46 1 -69zM0 -128l2 94q15 4 85 16t106 27q7 12 12.5 27t8.5 33.5t5.5 32.5t3 37.5t0.5 34v35.5v30q0 982 -22 1025q-4 8 -22 14.5t-44.5 11t-49.5 7t-48.5 4.5 t-30.5 3l-4 83q98 2 340 11.5t373 9.5q23 0 68.5 -0.5t67.5 -0.5q70 0 136.5 -13t128.5 -42t108 -71t74 -104.5t28 -137.5q0 -52 -16.5 -95.5t-39 -72t-64.5 -57.5t-73 -45t-84 -40q154 -35 256.5 -134t102.5 -248q0 -100 -35 -179.5t-93.5 -130.5t-138 -85.5t-163.5 -48.5 t-176 -14q-44 0 -132 3t-132 3q-106 0 -307 -11t-231 -12z" /> -<glyph unicode="" horiz-adv-x="1024" d="M0 -126l17 85q6 2 81.5 21.5t111.5 37.5q28 35 41 101q1 7 62 289t114 543.5t52 296.5v25q-24 13 -54.5 18.5t-69.5 8t-58 5.5l19 103q33 -2 120 -6.5t149.5 -7t120.5 -2.5q48 0 98.5 2.5t121 7t98.5 6.5q-5 -39 -19 -89q-30 -10 -101.5 -28.5t-108.5 -33.5 q-8 -19 -14 -42.5t-9 -40t-7.5 -45.5t-6.5 -42q-27 -148 -87.5 -419.5t-77.5 -355.5q-2 -9 -13 -58t-20 -90t-16 -83.5t-6 -57.5l1 -18q17 -4 185 -31q-3 -44 -16 -99q-11 0 -32.5 -1.5t-32.5 -1.5q-29 0 -87 10t-86 10q-138 2 -206 2q-51 0 -143 -9t-121 -11z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1744 128q33 0 42 -18.5t-11 -44.5l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80zM81 1407l54 -27q12 -5 211 -5q44 0 132 2 t132 2q36 0 107.5 -0.5t107.5 -0.5h293q6 0 21 -0.5t20.5 0t16 3t17.5 9t15 17.5l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 48t-14.5 73.5t-7.5 35.5q-6 8 -12 12.5t-15.5 6t-13 2.5t-18 0.5t-16.5 -0.5 q-17 0 -66.5 0.5t-74.5 0.5t-64 -2t-71 -6q-9 -81 -8 -136q0 -94 2 -388t2 -455q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27 q19 42 19 383q0 101 -3 303t-3 303v117q0 2 0.5 15.5t0.5 25t-1 25.5t-3 24t-5 14q-11 12 -162 12q-33 0 -93 -12t-80 -26q-19 -13 -34 -72.5t-31.5 -111t-42.5 -53.5q-42 26 -56 44v383z" /> -<glyph unicode="" d="M81 1407l54 -27q12 -5 211 -5q44 0 132 2t132 2q70 0 246.5 1t304.5 0.5t247 -4.5q33 -1 56 31l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 47.5t-15 73.5t-7 36q-10 13 -27 19q-5 2 -66 2q-30 0 -93 1t-103 1 t-94 -2t-96 -7q-9 -81 -8 -136l1 -152v52q0 -55 1 -154t1.5 -180t0.5 -153q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27 q7 16 11.5 74t6 145.5t1.5 155t-0.5 153.5t-0.5 89q0 7 -2.5 21.5t-2.5 22.5q0 7 0.5 44t1 73t0 76.5t-3 67.5t-6.5 32q-11 12 -162 12q-41 0 -163 -13.5t-138 -24.5q-19 -12 -34 -71.5t-31.5 -111.5t-42.5 -54q-42 26 -56 44v383zM1310 125q12 0 42 -19.5t57.5 -41.5 t59.5 -49t36 -30q26 -21 26 -49t-26 -49q-4 -3 -36 -30t-59.5 -49t-57.5 -41.5t-42 -19.5q-13 0 -20.5 10.5t-10 28.5t-2.5 33.5t1.5 33t1.5 19.5h-1024q0 -2 1.5 -19.5t1.5 -33t-2.5 -33.5t-10 -28.5t-20.5 -10.5q-12 0 -42 19.5t-57.5 41.5t-59.5 49t-36 30q-26 21 -26 49 t26 49q4 3 36 30t59.5 49t57.5 41.5t42 19.5q13 0 20.5 -10.5t10 -28.5t2.5 -33.5t-1.5 -33t-1.5 -19.5h1024q0 2 -1.5 19.5t-1.5 33t2.5 33.5t10 28.5t20.5 10.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45t-45 -19 h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M256 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM256 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5 t9.5 -22.5zM256 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344 q13 0 22.5 -9.5t9.5 -22.5zM256 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192 q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M384 992v-576q0 -13 -9.5 -22.5t-22.5 -9.5q-14 0 -23 9l-288 288q-9 9 -9 23t9 23l288 288q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M352 704q0 -14 -9 -23l-288 -288q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v576q0 13 9.5 22.5t22.5 9.5q14 0 23 -9l288 -288q9 -9 9 -23zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 1184v-1088q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-403 403v-166q0 -119 -84.5 -203.5t-203.5 -84.5h-704q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h704q119 0 203.5 -84.5t84.5 -203.5v-165l403 402q18 19 45 19q12 0 25 -5 q39 -17 39 -59z" /> -<glyph unicode="" horiz-adv-x="1920" d="M640 960q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 576v-448h-1408v192l320 320l160 -160l512 512zM1760 1280h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216 q0 13 -9.5 22.5t-22.5 9.5zM1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" /> -<glyph unicode="" d="M363 0l91 91l-235 235l-91 -91v-107h128v-128h107zM886 928q0 22 -22 22q-10 0 -17 -7l-542 -542q-7 -7 -7 -17q0 -22 22 -22q10 0 17 7l542 542q7 7 7 17zM832 1120l416 -416l-832 -832h-416v416zM1515 1024q0 -53 -37 -90l-166 -166l-416 416l166 165q36 38 90 38 q53 0 91 -38l235 -234q37 -39 37 -91z" /> -<glyph unicode="" horiz-adv-x="1024" d="M768 896q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1024 896q0 -109 -33 -179l-364 -774q-16 -33 -47.5 -52t-67.5 -19t-67.5 19t-46.5 52l-365 774q-33 70 -33 179q0 212 150 362t362 150t362 -150t150 -362z" /> -<glyph unicode="" d="M768 96v1088q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1024" d="M512 384q0 36 -20 69q-1 1 -15.5 22.5t-25.5 38t-25 44t-21 50.5q-4 16 -21 16t-21 -16q-7 -23 -21 -50.5t-25 -44t-25.5 -38t-15.5 -22.5q-20 -33 -20 -69q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 512q0 -212 -150 -362t-362 -150t-362 150t-150 362 q0 145 81 275q6 9 62.5 90.5t101 151t99.5 178t83 201.5q9 30 34 47t51 17t51.5 -17t33.5 -47q28 -93 83 -201.5t99.5 -178t101 -151t62.5 -90.5q81 -127 81 -275z" /> -<glyph unicode="" horiz-adv-x="1792" d="M888 352l116 116l-152 152l-116 -116v-56h96v-96h56zM1328 1072q-16 16 -33 -1l-350 -350q-17 -17 -1 -33t33 1l350 350q17 17 1 33zM1408 478v-190q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-14 -14 -32 -8q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v126q0 13 9 22l64 64q15 15 35 7t20 -29zM1312 1216l288 -288l-672 -672h-288v288zM1756 1084l-92 -92 l-288 288l92 92q28 28 68 28t68 -28l152 -152q28 -28 28 -68t-28 -68z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1408 547v-259q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h255v0q13 0 22.5 -9.5t9.5 -22.5q0 -27 -26 -32q-77 -26 -133 -60q-10 -4 -16 -4h-112q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832 q66 0 113 47t47 113v214q0 19 18 29q28 13 54 37q16 16 35 8q21 -9 21 -29zM1645 1043l-384 -384q-18 -19 -45 -19q-12 0 -25 5q-39 17 -39 59v192h-160q-323 0 -438 -131q-119 -137 -74 -473q3 -23 -20 -34q-8 -2 -12 -2q-16 0 -26 13q-10 14 -21 31t-39.5 68.5t-49.5 99.5 t-38.5 114t-17.5 122q0 49 3.5 91t14 90t28 88t47 81.5t68.5 74t94.5 61.5t124.5 48.5t159.5 30.5t196.5 11h160v192q0 42 39 59q13 5 25 5q26 0 45 -19l384 -384q19 -19 19 -45t-19 -45z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1408 606v-318q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-10 -10 -23 -10q-3 0 -9 2q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832 q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v254q0 13 9 22l64 64q10 10 23 10q6 0 12 -3q20 -8 20 -29zM1639 1095l-814 -814q-24 -24 -57 -24t-57 24l-430 430q-24 24 -24 57t24 57l110 110q24 24 57 24t57 -24l263 -263l647 647q24 24 57 24t57 -24l110 -110 q24 -24 24 -57t-24 -57z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-384v-384h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v384h-384v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45 t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h384v384h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-384h384v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" /> -<glyph unicode="" horiz-adv-x="1024" d="M979 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1747 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710 q19 19 32 13t13 -32v-710q4 11 13 19z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1619 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-8 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-19 19 -19 45t19 45l710 710q19 19 32 13t13 -32v-710q5 11 13 19z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1384 609l-1328 -738q-23 -13 -39.5 -3t-16.5 36v1472q0 26 16.5 36t39.5 -3l1328 -738q23 -13 23 -31t-23 -31z" /> -<glyph unicode="" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45zM640 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45z" /> -<glyph unicode="" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1664" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q19 -19 19 -45t-19 -45l-710 -710q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" /> -<glyph unicode="" horiz-adv-x="1792" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710 q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" /> -<glyph unicode="" horiz-adv-x="1024" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19z" /> -<glyph unicode="" horiz-adv-x="1538" d="M14 557l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13t13 32zM1473 0h-1408q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1171 1235l-531 -531l531 -531q19 -19 19 -45t-19 -45l-166 -166q-19 -19 -45 -19t-45 19l-742 742q-19 19 -19 45t19 45l742 742q19 19 45 19t45 -19l166 -166q19 -19 19 -45t-19 -45z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1107 659l-742 -742q-19 -19 -45 -19t-45 19l-166 166q-19 19 -19 45t19 45l531 531l-531 531q-19 19 -19 45t19 45l166 166q19 19 45 19t45 -19l742 -742q19 -19 19 -45t-19 -45z" /> -<glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1216 576v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" /> -<glyph unicode="" d="M1149 414q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45q0 -27 19 -46l90 -90q19 -19 46 -19 q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19l90 90q19 19 19 46zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1284 802q0 28 -18 46l-91 90q-19 19 -45 19t-45 -19l-408 -407l-226 226q-19 19 -45 19t-45 -19l-91 -90q-18 -18 -18 -46q0 -27 18 -45l362 -362q19 -19 45 -19q27 0 46 19l543 543q18 18 18 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M896 160v192q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h192q14 0 23 9t9 23zM1152 832q0 88 -55.5 163t-138.5 116t-170 41q-243 0 -371 -213q-15 -24 8 -42l132 -100q7 -6 19 -6q16 0 25 12q53 68 86 92q34 24 86 24q48 0 85.5 -26t37.5 -59 q0 -38 -20 -61t-68 -45q-63 -28 -115.5 -86.5t-52.5 -125.5v-36q0 -14 9 -23t23 -9h192q14 0 23 9t9 23q0 19 21.5 49.5t54.5 49.5q32 18 49 28.5t46 35t44.5 48t28 60.5t12.5 81zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1024 160v160q0 14 -9 23t-23 9h-96v512q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h96v-320h-96q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h448q14 0 23 9t9 23zM896 1056v160q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23 t23 -9h192q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1197 512h-109q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h109q-32 108 -112.5 188.5t-188.5 112.5v-109q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v109q-108 -32 -188.5 -112.5t-112.5 -188.5h109q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-109 q32 -108 112.5 -188.5t188.5 -112.5v109q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-109q108 32 188.5 112.5t112.5 188.5zM1536 704v-128q0 -26 -19 -45t-45 -19h-143q-37 -161 -154.5 -278.5t-278.5 -154.5v-143q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v143 q-161 37 -278.5 154.5t-154.5 278.5h-143q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h143q37 161 154.5 278.5t278.5 154.5v143q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-143q161 -37 278.5 -154.5t154.5 -278.5h143q26 0 45 -19t19 -45z" /> -<glyph unicode="" d="M1097 457l-146 -146q-10 -10 -23 -10t-23 10l-137 137l-137 -137q-10 -10 -23 -10t-23 10l-146 146q-10 10 -10 23t10 23l137 137l-137 137q-10 10 -10 23t10 23l146 146q10 10 23 10t23 -10l137 -137l137 137q10 10 23 10t23 -10l146 -146q10 -10 10 -23t-10 -23 l-137 -137l137 -137q10 -10 10 -23t-10 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5 t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1171 723l-422 -422q-19 -19 -45 -19t-45 19l-294 294q-19 19 -19 45t19 45l102 102q19 19 45 19t45 -19l147 -147l275 275q19 19 45 19t45 -19l102 -102q19 -19 19 -45t-19 -45zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198 t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1312 643q0 161 -87 295l-754 -753q137 -89 297 -89q111 0 211.5 43.5t173.5 116.5t116 174.5t43 212.5zM313 344l755 754q-135 91 -300 91q-148 0 -273 -73t-198 -199t-73 -274q0 -162 89 -299zM1536 643q0 -157 -61 -300t-163.5 -246t-245 -164t-298.5 -61t-298.5 61 t-245 164t-163.5 246t-61 300t61 299.5t163.5 245.5t245 164t298.5 61t298.5 -61t245 -164t163.5 -245.5t61 -299.5z" /> -<glyph unicode="" d="M1536 640v-128q0 -53 -32.5 -90.5t-84.5 -37.5h-704l293 -294q38 -36 38 -90t-38 -90l-75 -76q-37 -37 -90 -37q-52 0 -91 37l-651 652q-37 37 -37 90q0 52 37 91l651 650q38 38 91 38q52 0 90 -38l75 -74q38 -38 38 -91t-38 -91l-293 -293h704q52 0 84.5 -37.5 t32.5 -90.5z" /> -<glyph unicode="" d="M1472 576q0 -54 -37 -91l-651 -651q-39 -37 -91 -37q-51 0 -90 37l-75 75q-38 38 -38 91t38 91l293 293h-704q-52 0 -84.5 37.5t-32.5 90.5v128q0 53 32.5 90.5t84.5 37.5h704l-293 294q-38 36 -38 90t38 90l75 75q38 38 90 38q53 0 91 -38l651 -651q37 -35 37 -90z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1611 565q0 -51 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-294 293v-704q0 -52 -37.5 -84.5t-90.5 -32.5h-128q-53 0 -90.5 32.5t-37.5 84.5v704l-294 -293q-36 -38 -90 -38t-90 38l-75 75q-38 38 -38 90q0 53 38 91l651 651q35 37 90 37q54 0 91 -37l651 -651 q37 -39 37 -91z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1611 704q0 -53 -37 -90l-651 -652q-39 -37 -91 -37q-53 0 -90 37l-651 652q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l294 -294v704q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-704l294 294q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 896q0 -26 -19 -45l-512 -512q-19 -19 -45 -19t-45 19t-19 45v256h-224q-98 0 -175.5 -6t-154 -21.5t-133 -42.5t-105.5 -69.5t-80 -101t-48.5 -138.5t-17.5 -181q0 -55 5 -123q0 -6 2.5 -23.5t2.5 -26.5q0 -15 -8.5 -25t-23.5 -10q-16 0 -28 17q-7 9 -13 22 t-13.5 30t-10.5 24q-127 285 -127 451q0 199 53 333q162 403 875 403h224v256q0 26 19 45t45 19t45 -19l512 -512q19 -19 19 -45z" /> -<glyph unicode="" d="M755 480q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23zM1536 1344v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332 q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45z" /> -<glyph unicode="" d="M768 576v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45zM1523 1248q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45 t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-416v-416q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v416h-416q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h416v416q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-416h416q40 0 68 -28t28 -68z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-1216q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h1216q40 0 68 -28t28 -68z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1482 486q46 -26 59.5 -77.5t-12.5 -97.5l-64 -110q-26 -46 -77.5 -59.5t-97.5 12.5l-266 153v-307q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v307l-266 -153q-46 -26 -97.5 -12.5t-77.5 59.5l-64 110q-26 46 -12.5 97.5t59.5 77.5l266 154l-266 154 q-46 26 -59.5 77.5t12.5 97.5l64 110q26 46 77.5 59.5t97.5 -12.5l266 -153v307q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-307l266 153q46 26 97.5 12.5t77.5 -59.5l64 -110q26 -46 12.5 -97.5t-59.5 -77.5l-266 -154z" /> -<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM896 161v190q0 14 -9 23.5t-22 9.5h-192q-13 0 -23 -10t-10 -23v-190q0 -13 10 -23t23 -10h192 q13 0 22 9.5t9 23.5zM894 505l18 621q0 12 -10 18q-10 8 -24 8h-220q-14 0 -24 -8q-10 -6 -10 -18l17 -621q0 -10 10 -17.5t24 -7.5h185q14 0 23.5 7.5t10.5 17.5z" /> -<glyph unicode="" d="M928 180v56v468v192h-320v-192v-468v-56q0 -25 18 -38.5t46 -13.5h192q28 0 46 13.5t18 38.5zM472 1024h195l-126 161q-26 31 -69 31q-40 0 -68 -28t-28 -68t28 -68t68 -28zM1160 1120q0 40 -28 68t-68 28q-43 0 -69 -31l-125 -161h194q40 0 68 28t28 68zM1536 864v-320 q0 -14 -9 -23t-23 -9h-96v-416q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v416h-96q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h440q-93 0 -158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5q107 0 168 -77l128 -165l128 165q61 77 168 77q93 0 158.5 -65.5t65.5 -158.5 t-65.5 -158.5t-158.5 -65.5h440q14 0 23 -9t9 -23z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19q-172 0 -318 -49.5t-259.5 -134t-235.5 -219.5q-19 -21 -19 -45q0 -26 19 -45t45 -19q24 0 45 19q27 24 74 71t67 66q137 124 268.5 176t313.5 52q26 0 45 19t19 45zM1792 1030q0 -95 -20 -193q-46 -224 -184.5 -383t-357.5 -268 q-214 -108 -438 -108q-148 0 -286 47q-15 5 -88 42t-96 37q-16 0 -39.5 -32t-45 -70t-52.5 -70t-60 -32q-30 0 -51 11t-31 24t-27 42q-2 4 -6 11t-5.5 10t-3 9.5t-1.5 13.5q0 35 31 73.5t68 65.5t68 56t31 48q0 4 -14 38t-16 44q-9 51 -9 104q0 115 43.5 220t119 184.5 t170.5 139t204 95.5q55 18 145 25.5t179.5 9t178.5 6t163.5 24t113.5 56.5l29.5 29.5t29.5 28t27 20t36.5 16t43.5 4.5q39 0 70.5 -46t47.5 -112t24 -124t8 -96z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1408 -160v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1152 896q0 -78 -24.5 -144t-64 -112.5t-87.5 -88t-96 -77.5t-87.5 -72t-64 -81.5t-24.5 -96.5q0 -96 67 -224l-4 1l1 -1 q-90 41 -160 83t-138.5 100t-113.5 122.5t-72.5 150.5t-27.5 184q0 78 24.5 144t64 112.5t87.5 88t96 77.5t87.5 72t64 81.5t24.5 96.5q0 94 -66 224l3 -1l-1 1q90 -41 160 -83t138.5 -100t113.5 -122.5t72.5 -150.5t27.5 -184z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1664 576q-152 236 -381 353q61 -104 61 -225q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 121 61 225q-229 -117 -381 -353q133 -205 333.5 -326.5t434.5 -121.5t434.5 121.5t333.5 326.5zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5 t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1792 576q0 -34 -20 -69q-140 -230 -376.5 -368.5t-499.5 -138.5t-499.5 139t-376.5 368q-20 35 -20 69t20 69q140 229 376.5 368t499.5 139t499.5 -139t376.5 -368q20 -35 20 -69z" /> -<glyph unicode="" horiz-adv-x="1792" d="M555 201l78 141q-87 63 -136 159t-49 203q0 121 61 225q-229 -117 -381 -353q167 -258 427 -375zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1307 1151q0 -7 -1 -9 q-105 -188 -315 -566t-316 -567l-49 -89q-10 -16 -28 -16q-12 0 -134 70q-16 10 -16 28q0 12 44 87q-143 65 -263.5 173t-208.5 245q-20 31 -20 69t20 69q153 235 380 371t496 136q89 0 180 -17l54 97q10 16 28 16q5 0 18 -6t31 -15.5t33 -18.5t31.5 -18.5t19.5 -11.5 q16 -10 16 -27zM1344 704q0 -139 -79 -253.5t-209 -164.5l280 502q8 -45 8 -84zM1792 576q0 -35 -20 -69q-39 -64 -109 -145q-150 -172 -347.5 -267t-419.5 -95l74 132q212 18 392.5 137t301.5 307q-115 179 -282 294l63 112q95 -64 182.5 -153t144.5 -184q20 -34 20 -69z " /> -<glyph unicode="" horiz-adv-x="1792" d="M1024 161v190q0 14 -9.5 23.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -23.5v-190q0 -14 9.5 -23.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 23.5zM1022 535l18 459q0 12 -10 19q-13 11 -24 11h-220q-11 0 -24 -11q-10 -7 -10 -21l17 -457q0 -10 10 -16.5t24 -6.5h185 q14 0 23.5 6.5t10.5 16.5zM1008 1469l768 -1408q35 -63 -2 -126q-17 -29 -46.5 -46t-63.5 -17h-1536q-34 0 -63.5 17t-46.5 46q-37 63 -2 126l768 1408q17 31 47 49t65 18t65 -18t47 -49z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1376 1376q44 -52 12 -148t-108 -172l-161 -161l160 -696q5 -19 -12 -33l-128 -96q-7 -6 -19 -6q-4 0 -7 1q-15 3 -21 16l-279 508l-259 -259l53 -194q5 -17 -8 -31l-96 -96q-9 -9 -23 -9h-2q-15 2 -24 13l-189 252l-252 189q-11 7 -13 23q-1 13 9 25l96 97q9 9 23 9 q6 0 8 -1l194 -53l259 259l-508 279q-14 8 -17 24q-2 16 9 27l128 128q14 13 30 8l665 -159l160 160q76 76 172 108t148 -12z" /> -<glyph unicode="" horiz-adv-x="1664" d="M128 -128h288v288h-288v-288zM480 -128h320v288h-320v-288zM128 224h288v320h-288v-320zM480 224h320v320h-320v-320zM128 608h288v288h-288v-288zM864 -128h320v288h-320v-288zM480 608h320v288h-320v-288zM1248 -128h288v288h-288v-288zM864 224h320v320h-320v-320z M512 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1248 224h288v320h-288v-320zM864 608h320v288h-320v-288zM1248 608h288v288h-288v-288zM1280 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64 q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47 h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" /> -<glyph unicode="" horiz-adv-x="1792" d="M666 1055q-60 -92 -137 -273q-22 45 -37 72.5t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q250 0 410 -225zM1792 256q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192q-32 0 -85 -0.5t-81 -1t-73 1 t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1792 1152q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5 v192h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111 t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281 q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5z" /> -<glyph unicode="" d="M1536 704v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5t-98.5 362v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384 q26 0 45 -19t19 -45zM512 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45zM1536 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1683 205l-166 -165q-19 -19 -45 -19t-45 19l-531 531l-531 -531q-19 -19 -45 -19t-45 19l-166 165q-19 19 -19 45.5t19 45.5l742 741q19 19 45 19t45 -19l742 -741q19 -19 19 -45.5t-19 -45.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1683 728l-742 -741q-19 -19 -45 -19t-45 19l-742 741q-19 19 -19 45.5t19 45.5l166 165q19 19 45 19t45 -19l531 -531l531 531q19 19 45 19t45 -19l166 -165q19 -19 19 -45.5t-19 -45.5z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " /> -<glyph unicode="" horiz-adv-x="1664" d="M640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5 l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5 t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" /> -<glyph unicode="" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" /> -<glyph unicode="" horiz-adv-x="2048" d="M640 640v-512h-256v512h256zM1024 1152v-1024h-256v1024h256zM2048 0v-128h-2048v1536h128v-1408h1920zM1408 896v-768h-256v768h256zM1792 1280v-1152h-256v1152h256z" /> -<glyph unicode="" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M1536 160q0 -119 -84.5 -203.5t-203.5 -84.5h-192v608h203l30 224h-233v143q0 54 28 83t96 29l132 1v207q-96 9 -180 9q-136 0 -218 -80.5t-82 -225.5v-166h-224v-224h224v-608h-544q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960 q119 0 203.5 -84.5t84.5 -203.5v-960z" /> -<glyph unicode="" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" /> -<glyph unicode="" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1408 768q0 -139 -94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224 q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257zM1792 512q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7 q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230z" /> -<glyph unicode="" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 768q0 51 -39 89.5t-89 38.5h-352q0 58 48 159.5t48 160.5q0 98 -32 145t-128 47q-26 -26 -38 -85t-30.5 -125.5t-59.5 -109.5q-22 -23 -77 -91q-4 -5 -23 -30t-31.5 -41t-34.5 -42.5 t-40 -44t-38.5 -35.5t-40 -27t-35.5 -9h-32v-640h32q13 0 31.5 -3t33 -6.5t38 -11t35 -11.5t35.5 -12.5t29 -10.5q211 -73 342 -73h121q192 0 192 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5q32 1 53.5 47t21.5 81zM1536 769 q0 -89 -49 -163q9 -33 9 -69q0 -77 -38 -144q3 -21 3 -43q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5h-36h-93q-96 0 -189.5 22.5t-216.5 65.5q-116 40 -138 40h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h274q36 24 137 155q58 75 107 128 q24 25 35.5 85.5t30.5 126.5t62 108q39 37 90 37q84 0 151 -32.5t102 -101.5t35 -186q0 -93 -48 -192h176q104 0 180 -76t76 -179z" /> -<glyph unicode="" d="M256 1088q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 512q0 35 -21.5 81t-53.5 47q15 17 25 47.5t10 55.5q0 69 -53 119q18 32 18 69t-17.5 73.5t-47.5 52.5q5 30 5 56q0 85 -49 126t-136 41h-128q-131 0 -342 -73q-5 -2 -29 -10.5 t-35.5 -12.5t-35 -11.5t-38 -11t-33 -6.5t-31.5 -3h-32v-640h32q16 0 35.5 -9t40 -27t38.5 -35.5t40 -44t34.5 -42.5t31.5 -41t23 -30q55 -68 77 -91q41 -43 59.5 -109.5t30.5 -125.5t38 -85q96 0 128 47t32 145q0 59 -48 160.5t-48 159.5h352q50 0 89 38.5t39 89.5z M1536 511q0 -103 -76 -179t-180 -76h-176q48 -99 48 -192q0 -118 -35 -186q-35 -69 -102 -101.5t-151 -32.5q-51 0 -90 37q-34 33 -54 82t-25.5 90.5t-17.5 84.5t-31 64q-48 50 -107 127q-101 131 -137 155h-274q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5 h288q22 0 138 40q128 44 223 66t200 22h112q140 0 226.5 -79t85.5 -216v-5q60 -77 60 -178q0 -22 -3 -43q38 -67 38 -144q0 -36 -9 -69q49 -74 49 -163z" /> -<glyph unicode="" horiz-adv-x="896" d="M832 1504v-1339l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1664 940q0 81 -21.5 143t-55 98.5t-81.5 59.5t-94 31t-98 8t-112 -25.5t-110.5 -64t-86.5 -72t-60 -61.5q-18 -22 -49 -22t-49 22q-24 28 -60 61.5t-86.5 72t-110.5 64t-112 25.5t-98 -8t-94 -31t-81.5 -59.5t-55 -98.5t-21.5 -143q0 -168 187 -355l581 -560l580 559 q188 188 188 356zM1792 940q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5 q224 0 351 -124t127 -344z" /> -<glyph unicode="" horiz-adv-x="1664" d="M640 96q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h320q13 0 22.5 -9.5t9.5 -22.5q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-66 0 -113 -47t-47 -113v-704 q0 -66 47 -113t113 -47h288h11h13t11.5 -1t11.5 -3t8 -5.5t7 -9t2 -13.5zM1568 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45z" /> -<glyph unicode="" d="M237 122h231v694h-231v-694zM483 1030q-1 52 -36 86t-93 34t-94.5 -34t-36.5 -86q0 -51 35.5 -85.5t92.5 -34.5h1q59 0 95 34.5t36 85.5zM1068 122h231v398q0 154 -73 233t-193 79q-136 0 -209 -117h2v101h-231q3 -66 0 -694h231v388q0 38 7 56q15 35 45 59.5t74 24.5 q116 0 116 -157v-371zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1152" d="M480 672v448q0 14 -9 23t-23 9t-23 -9t-9 -23v-448q0 -14 9 -23t23 -9t23 9t9 23zM1152 320q0 -26 -19 -45t-45 -19h-429l-51 -483q-2 -12 -10.5 -20.5t-20.5 -8.5h-1q-27 0 -32 27l-76 485h-404q-26 0 -45 19t-19 45q0 123 78.5 221.5t177.5 98.5v512q-52 0 -90 38 t-38 90t38 90t90 38h640q52 0 90 -38t38 -90t-38 -90t-90 -38v-512q99 0 177.5 -98.5t78.5 -221.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1408 608v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1792 1472v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45z" /> -<glyph unicode="" d="M1184 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45zM1536 992v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5 q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M458 653q-74 162 -74 371h-256v-96q0 -78 94.5 -162t235.5 -113zM1536 928v96h-256q0 -209 -74 -371q141 29 235.5 113t94.5 162zM1664 1056v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91 t97.5 -37q75 0 133.5 -45.5t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143v128q0 40 28 68t68 28h288v96 q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68z" /> -<glyph unicode="" d="M394 184q-8 -9 -20 3q-13 11 -4 19q8 9 20 -3q12 -11 4 -19zM352 245q9 -12 0 -19q-8 -6 -17 7t0 18q9 7 17 -6zM291 305q-5 -7 -13 -2q-10 5 -7 12q3 5 13 2q10 -5 7 -12zM322 271q-6 -7 -16 3q-9 11 -2 16q6 6 16 -3q9 -11 2 -16zM451 159q-4 -12 -19 -6q-17 4 -13 15 t19 7q16 -5 13 -16zM514 154q0 -11 -16 -11q-17 -2 -17 11q0 11 16 11q17 2 17 -11zM572 164q2 -10 -14 -14t-18 8t14 15q16 2 18 -9zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-224q-16 0 -24.5 1t-19.5 5t-16 14.5t-5 27.5v239q0 97 -52 142q57 6 102.5 18t94 39 t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103 q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -103t0.5 -68q0 -22 -11 -33.5t-22 -13t-33 -1.5 h-224q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1280 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 288v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h427q21 -56 70.5 -92 t110.5 -36h256q61 0 110.5 36t70.5 92h427q40 0 68 -28t28 -68zM1339 936q-17 -40 -59 -40h-256v-448q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-42 0 -59 40q-17 39 14 69l448 448q18 19 45 19t45 -19l448 -448q31 -30 14 -69z" /> -<glyph unicode="" d="M1407 710q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5 q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275zM1535 712q0 -165 -70 -327.5t-196 -288t-281 -180.5q-124 -44 -326 -44 q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5 q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1408 296q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5 q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235t235 -174 q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1120 1280h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1152 1280h-1024v-1242l423 406l89 85l89 -85l423 -406v1242zM1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289 q0 34 19.5 62t52.5 41q21 9 44 9h1048z" /> -<glyph unicode="" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1620 1128q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41 q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50z" /> -<glyph unicode="" horiz-adv-x="1024" d="M959 1524v-264h-157q-86 0 -116 -36t-30 -108v-189h293l-39 -296h-254v-759h-306v759h-255v296h255v218q0 186 104 288.5t277 102.5q147 0 228 -12z" /> -<glyph unicode="" d="M1536 640q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5 q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23 q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1664 960v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5 t316.5 -131.5t131.5 -316.5z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" /> -<glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" /> -<glyph unicode="" d="M1040 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1296 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1408 160v320q0 13 -9.5 22.5t-22.5 9.5 h-1216q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM1536 480v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113v320q0 25 16 75 l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1664 896q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5 t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384zM1536 292v954q-394 -302 -768 -343v-270q377 -42 768 -341z" /> -<glyph unicode="" horiz-adv-x="1792" d="M912 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM246 128h1300q-266 300 -266 832q0 51 -24 105t-69 103t-121.5 80.5t-169.5 31.5t-169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -532 -266 -832z M1728 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q190 -28 307 -158.5 t117 -282.5q0 -139 19.5 -260t50 -206t74.5 -158.5t85 -119.5t91 -88z" /> -<glyph unicode="" d="M1376 640l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53q-41 -12 -70 19q-31 29 -19 70 l53 186l-188 48q-40 10 -52 51q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70 l-53 -186l188 -48q40 -10 52 -51q10 -42 -20 -70z" /> -<glyph unicode="" horiz-adv-x="1792" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 768q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106 q-69 -57 -140 -57h-32v-640h32q72 0 167 -32t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90zM1792 769q0 -105 -75.5 -181t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5 t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1376 128h32v640h-32q-35 0 -67.5 12t-62.5 37t-50 46t-49 54q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576 q-50 0 -89 -38.5t-39 -89.5q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32zM1664 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45 t45 -19t45 19t19 45zM1792 768v-640q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181q0 103 76 179t180 76h374q-22 60 -22 128 q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5z" /> -<glyph unicode="" d="M1280 -64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 700q0 189 -167 189q-26 0 -56 -5q-16 30 -52.5 47.5t-73.5 17.5t-69 -18q-50 53 -119 53q-25 0 -55.5 -10t-47.5 -25v331q0 52 -38 90t-90 38q-51 0 -89.5 -39t-38.5 -89v-576 q-20 0 -48.5 15t-55 33t-68 33t-84.5 15q-67 0 -97.5 -44.5t-30.5 -115.5q0 -24 139 -90q44 -24 65 -37q64 -40 145 -112q81 -71 106 -101q57 -69 57 -140v-32h640v32q0 72 32 167t64 193.5t32 179.5zM1536 705q0 -133 -69 -322q-59 -164 -59 -223v-288q0 -53 -37.5 -90.5 t-90.5 -37.5h-640q-53 0 -90.5 37.5t-37.5 90.5v288q0 10 -4.5 21.5t-14 23.5t-18 22.5t-22.5 24t-21.5 20.5t-21.5 19t-17 14q-74 65 -129 100q-21 13 -62 33t-72 37t-63 40.5t-49.5 55t-17.5 69.5q0 125 67 206.5t189 81.5q68 0 128 -22v374q0 104 76 180t179 76 q105 0 181 -75.5t76 -180.5v-169q62 -4 119 -37q21 3 43 3q101 0 178 -60q139 1 219.5 -85t80.5 -227z" /> -<glyph unicode="" d="M1408 576q0 84 -32 183t-64 194t-32 167v32h-640v-32q0 -35 -12 -67.5t-37 -62.5t-46 -50t-54 -49q-9 -8 -14 -12q-81 -72 -145 -112q-22 -14 -68 -38q-3 -1 -22.5 -10.5t-36 -18.5t-35.5 -20t-30.5 -21.5t-11.5 -18.5q0 -71 30.5 -115.5t97.5 -44.5q43 0 84.5 15t68 33 t55 33t48.5 15v-576q0 -50 38.5 -89t89.5 -39q52 0 90 38t38 90v331q46 -35 103 -35q69 0 119 53q32 -18 69 -18t73.5 17.5t52.5 47.5q24 -4 56 -4q85 0 126 48.5t41 135.5zM1280 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 580 q0 -142 -77.5 -230t-217.5 -87l-5 1q-76 -61 -178 -61q-22 0 -43 3q-54 -30 -119 -37v-169q0 -105 -76 -180.5t-181 -75.5q-103 0 -179 76t-76 180v374q-54 -22 -128 -22q-121 0 -188.5 81.5t-67.5 206.5q0 38 17.5 69.5t49.5 55t63 40.5t72 37t62 33q55 35 129 100 q3 2 17 14t21.5 19t21.5 20.5t22.5 24t18 22.5t14 23.5t4.5 21.5v288q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-288q0 -59 59 -223q69 -190 69 -317z" /> -<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-502l189 189q19 19 19 45t-19 45l-91 91q-18 18 -45 18t-45 -18l-362 -362l-91 -91q-18 -18 -18 -45t18 -45l91 -91l362 -362q18 -18 45 -18t45 18l91 91q18 18 18 45t-18 45l-189 189h502q26 0 45 19t19 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1285 640q0 27 -18 45l-91 91l-362 362q-18 18 -45 18t-45 -18l-91 -91q-18 -18 -18 -45t18 -45l189 -189h-502q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h502l-189 -189q-19 -19 -19 -45t19 -45l91 -91q18 -18 45 -18t45 18l362 362l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1284 641q0 27 -18 45l-362 362l-91 91q-18 18 -45 18t-45 -18l-91 -91l-362 -362q-18 -18 -18 -45t18 -45l91 -91q18 -18 45 -18t45 18l189 189v-502q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v502l189 -189q19 -19 45 -19t45 19l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1284 639q0 27 -18 45l-91 91q-18 18 -45 18t-45 -18l-189 -189v502q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-502l-189 189q-19 19 -45 19t-45 -19l-91 -91q-18 -18 -18 -45t18 -45l362 -362l91 -91q18 -18 45 -18t45 18l91 91l362 362q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1042 887q-2 -1 -9.5 -9.5t-13.5 -9.5q2 0 4.5 5t5 11t3.5 7q6 7 22 15q14 6 52 12q34 8 51 -11 q-2 2 9.5 13t14.5 12q3 2 15 4.5t15 7.5l2 22q-12 -1 -17.5 7t-6.5 21q0 -2 -6 -8q0 7 -4.5 8t-11.5 -1t-9 -1q-10 3 -15 7.5t-8 16.5t-4 15q-2 5 -9.5 10.5t-9.5 10.5q-1 2 -2.5 5.5t-3 6.5t-4 5.5t-5.5 2.5t-7 -5t-7.5 -10t-4.5 -5q-3 2 -6 1.5t-4.5 -1t-4.5 -3t-5 -3.5 q-3 -2 -8.5 -3t-8.5 -2q15 5 -1 11q-10 4 -16 3q9 4 7.5 12t-8.5 14h5q-1 4 -8.5 8.5t-17.5 8.5t-13 6q-8 5 -34 9.5t-33 0.5q-5 -6 -4.5 -10.5t4 -14t3.5 -12.5q1 -6 -5.5 -13t-6.5 -12q0 -7 14 -15.5t10 -21.5q-3 -8 -16 -16t-16 -12q-5 -8 -1.5 -18.5t10.5 -16.5 q2 -2 1.5 -4t-3.5 -4.5t-5.5 -4t-6.5 -3.5l-3 -2q-11 -5 -20.5 6t-13.5 26q-7 25 -16 30q-23 8 -29 -1q-5 13 -41 26q-25 9 -58 4q6 1 0 15q-7 15 -19 12q3 6 4 17.5t1 13.5q3 13 12 23q1 1 7 8.5t9.5 13.5t0.5 6q35 -4 50 11q5 5 11.5 17t10.5 17q9 6 14 5.5t14.5 -5.5 t14.5 -5q14 -1 15.5 11t-7.5 20q12 -1 3 17q-5 7 -8 9q-12 4 -27 -5q-8 -4 2 -8q-1 1 -9.5 -10.5t-16.5 -17.5t-16 5q-1 1 -5.5 13.5t-9.5 13.5q-8 0 -16 -15q3 8 -11 15t-24 8q19 12 -8 27q-7 4 -20.5 5t-19.5 -4q-5 -7 -5.5 -11.5t5 -8t10.5 -5.5t11.5 -4t8.5 -3 q14 -10 8 -14q-2 -1 -8.5 -3.5t-11.5 -4.5t-6 -4q-3 -4 0 -14t-2 -14q-5 5 -9 17.5t-7 16.5q7 -9 -25 -6l-10 1q-4 0 -16 -2t-20.5 -1t-13.5 8q-4 8 0 20q1 4 4 2q-4 3 -11 9.5t-10 8.5q-46 -15 -94 -41q6 -1 12 1q5 2 13 6.5t10 5.5q34 14 42 7l5 5q14 -16 20 -25 q-7 4 -30 1q-20 -6 -22 -12q7 -12 5 -18q-4 3 -11.5 10t-14.5 11t-15 5q-16 0 -22 -1q-146 -80 -235 -222q7 -7 12 -8q4 -1 5 -9t2.5 -11t11.5 3q9 -8 3 -19q1 1 44 -27q19 -17 21 -21q3 -11 -10 -18q-1 2 -9 9t-9 4q-3 -5 0.5 -18.5t10.5 -12.5q-7 0 -9.5 -16t-2.5 -35.5 t-1 -23.5l2 -1q-3 -12 5.5 -34.5t21.5 -19.5q-13 -3 20 -43q6 -8 8 -9q3 -2 12 -7.5t15 -10t10 -10.5q4 -5 10 -22.5t14 -23.5q-2 -6 9.5 -20t10.5 -23q-1 0 -2.5 -1t-2.5 -1q3 -7 15.5 -14t15.5 -13q1 -3 2 -10t3 -11t8 -2q2 20 -24 62q-15 25 -17 29q-3 5 -5.5 15.5 t-4.5 14.5q2 0 6 -1.5t8.5 -3.5t7.5 -4t2 -3q-3 -7 2 -17.5t12 -18.5t17 -19t12 -13q6 -6 14 -19.5t0 -13.5q9 0 20 -10t17 -20q5 -8 8 -26t5 -24q2 -7 8.5 -13.5t12.5 -9.5l16 -8t13 -7q5 -2 18.5 -10.5t21.5 -11.5q10 -4 16 -4t14.5 2.5t13.5 3.5q15 2 29 -15t21 -21 q36 -19 55 -11q-2 -1 0.5 -7.5t8 -15.5t9 -14.5t5.5 -8.5q5 -6 18 -15t18 -15q6 4 7 9q-3 -8 7 -20t18 -10q14 3 14 32q-31 -15 -49 18q0 1 -2.5 5.5t-4 8.5t-2.5 8.5t0 7.5t5 3q9 0 10 3.5t-2 12.5t-4 13q-1 8 -11 20t-12 15q-5 -9 -16 -8t-16 9q0 -1 -1.5 -5.5t-1.5 -6.5 q-13 0 -15 1q1 3 2.5 17.5t3.5 22.5q1 4 5.5 12t7.5 14.5t4 12.5t-4.5 9.5t-17.5 2.5q-19 -1 -26 -20q-1 -3 -3 -10.5t-5 -11.5t-9 -7q-7 -3 -24 -2t-24 5q-13 8 -22.5 29t-9.5 37q0 10 2.5 26.5t3 25t-5.5 24.5q3 2 9 9.5t10 10.5q2 1 4.5 1.5t4.5 0t4 1.5t3 6q-1 1 -4 3 q-3 3 -4 3q7 -3 28.5 1.5t27.5 -1.5q15 -11 22 2q0 1 -2.5 9.5t-0.5 13.5q5 -27 29 -9q3 -3 15.5 -5t17.5 -5q3 -2 7 -5.5t5.5 -4.5t5 0.5t8.5 6.5q10 -14 12 -24q11 -40 19 -44q7 -3 11 -2t4.5 9.5t0 14t-1.5 12.5l-1 8v18l-1 8q-15 3 -18.5 12t1.5 18.5t15 18.5q1 1 8 3.5 t15.5 6.5t12.5 8q21 19 15 35q7 0 11 9q-1 0 -5 3t-7.5 5t-4.5 2q9 5 2 16q5 3 7.5 11t7.5 10q9 -12 21 -2q7 8 1 16q5 7 20.5 10.5t18.5 9.5q7 -2 8 2t1 12t3 12q4 5 15 9t13 5l17 11q3 4 0 4q18 -2 31 11q10 11 -6 20q3 6 -3 9.5t-15 5.5q3 1 11.5 0.5t10.5 1.5 q15 10 -7 16q-17 5 -43 -12zM879 10q206 36 351 189q-3 3 -12.5 4.5t-12.5 3.5q-18 7 -24 8q1 7 -2.5 13t-8 9t-12.5 8t-11 7q-2 2 -7 6t-7 5.5t-7.5 4.5t-8.5 2t-10 -1l-3 -1q-3 -1 -5.5 -2.5t-5.5 -3t-4 -3t0 -2.5q-21 17 -36 22q-5 1 -11 5.5t-10.5 7t-10 1.5t-11.5 -7 q-5 -5 -6 -15t-2 -13q-7 5 0 17.5t2 18.5q-3 6 -10.5 4.5t-12 -4.5t-11.5 -8.5t-9 -6.5t-8.5 -5.5t-8.5 -7.5q-3 -4 -6 -12t-5 -11q-2 4 -11.5 6.5t-9.5 5.5q2 -10 4 -35t5 -38q7 -31 -12 -48q-27 -25 -29 -40q-4 -22 12 -26q0 -7 -8 -20.5t-7 -21.5q0 -6 2 -16z" /> -<glyph unicode="" horiz-adv-x="1664" d="M384 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1028 484l-682 -682q-37 -37 -90 -37q-52 0 -91 37l-106 108q-38 36 -38 90q0 53 38 91l681 681q39 -98 114.5 -173.5t173.5 -114.5zM1662 919q0 -39 -23 -106q-47 -134 -164.5 -217.5 t-258.5 -83.5q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q58 0 121.5 -16.5t107.5 -46.5q16 -11 16 -28t-16 -28l-293 -169v-224l193 -107q5 3 79 48.5t135.5 81t70.5 35.5q15 0 23.5 -10t8.5 -25z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1024 128h640v128h-640v-128zM640 640h1024v128h-1024v-128zM1280 1152h384v128h-384v-128zM1792 320v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 832v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19 t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1403 1241q17 -41 -14 -70l-493 -493v-742q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-256 256q-19 19 -19 45v486l-493 493q-31 29 -14 70q17 39 59 39h1280q42 0 59 -39z" /> -<glyph unicode="" horiz-adv-x="1792" d="M640 1280h512v128h-512v-128zM1792 640v-480q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v480h672v-160q0 -26 19 -45t45 -19h320q26 0 45 19t19 45v160h672zM1024 640v-128h-256v128h256zM1792 1120v-384h-1792v384q0 66 47 113t113 47h352v160q0 40 28 68 t68 28h576q40 0 68 -28t28 -68v-160h352q66 0 113 -47t47 -113z" /> -<glyph unicode="" d="M1283 995l-355 -355l355 -355l144 144q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l144 144l-355 355l-355 -355l144 -144q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l144 -144 l355 355l-355 355l-144 -144q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v448q0 26 19 45t45 19h448q42 0 59 -40q17 -39 -14 -69l-144 -144l355 -355l355 355l-144 144q-31 30 -14 69q17 40 59 40h448q26 0 45 -19t19 -45v-448q0 -42 -39 -59q-13 -5 -25 -5q-26 0 -45 19z " /> -<glyph unicode="" horiz-adv-x="1920" d="M593 640q-162 -5 -265 -128h-134q-82 0 -138 40.5t-56 118.5q0 353 124 353q6 0 43.5 -21t97.5 -42.5t119 -21.5q67 0 133 23q-5 -37 -5 -66q0 -139 81 -256zM1664 3q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q10 0 43 -21.5t73 -48t107 -48t135 -21.5t135 21.5t107 48t73 48t43 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM640 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75 t75 -181zM1344 896q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5zM1920 671q0 -78 -56 -118.5t-138 -40.5h-134q-103 123 -265 128q81 117 81 256q0 29 -5 66q66 -23 133 -23q59 0 119 21.5t97.5 42.5 t43.5 21q124 0 124 -353zM1792 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1456 320q0 40 -28 68l-208 208q-28 28 -68 28q-42 0 -72 -32q3 -3 19 -18.5t21.5 -21.5t15 -19t13 -25.5t3.5 -27.5q0 -40 -28 -68t-68 -28q-15 0 -27.5 3.5t-25.5 13t-19 15t-21.5 21.5t-18.5 19q-33 -31 -33 -73q0 -40 28 -68l206 -207q27 -27 68 -27q40 0 68 26 l147 146q28 28 28 67zM753 1025q0 40 -28 68l-206 207q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l208 -208q27 -27 68 -27q42 0 72 31q-3 3 -19 18.5t-21.5 21.5t-15 19t-13 25.5t-3.5 27.5q0 40 28 68t68 28q15 0 27.5 -3.5t25.5 -13t19 -15 t21.5 -21.5t18.5 -19q33 31 33 73zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-206 207q-83 83 -83 203q0 123 88 209l-88 88q-86 -88 -208 -88q-120 0 -204 84l-208 208q-84 84 -84 204t85 203l147 146q83 83 203 83q121 0 204 -85l206 -207 q83 -83 83 -203q0 -123 -88 -209l88 -88q86 88 208 88q120 0 204 -84l208 -208q84 -84 84 -204z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5q0 132 71 241.5t187 163.5q-2 28 -2 43q0 212 150 362t362 150q158 0 286.5 -88t187.5 -230q70 62 166 62q106 0 181 -75t75 -181q0 -75 -41 -138q129 -30 213 -134.5t84 -239.5z " /> -<glyph unicode="" horiz-adv-x="1664" d="M1527 88q56 -89 21.5 -152.5t-140.5 -63.5h-1152q-106 0 -140.5 63.5t21.5 152.5l503 793v399h-64q-26 0 -45 19t-19 45t19 45t45 19h512q26 0 45 -19t19 -45t-19 -45t-45 -19h-64v-399zM748 813l-272 -429h712l-272 429l-20 31v37v399h-128v-399v-37z" /> -<glyph unicode="" horiz-adv-x="1792" d="M960 640q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM1260 576l507 -398q28 -20 25 -56q-5 -35 -35 -51l-128 -64q-13 -7 -29 -7q-17 0 -31 8l-690 387l-110 -66q-8 -4 -12 -5q14 -49 10 -97q-7 -77 -56 -147.5t-132 -123.5q-132 -84 -277 -84 q-136 0 -222 78q-90 84 -79 207q7 76 56 147t131 124q132 84 278 84q83 0 151 -31q9 13 22 22l122 73l-122 73q-13 9 -22 22q-68 -31 -151 -31q-146 0 -278 84q-82 53 -131 124t-56 147q-5 59 15.5 113t63.5 93q85 79 222 79q145 0 277 -84q83 -52 132 -123t56 -148 q4 -48 -10 -97q4 -1 12 -5l110 -66l690 387q14 8 31 8q16 0 29 -7l128 -64q30 -16 35 -51q3 -36 -25 -56zM579 836q46 42 21 108t-106 117q-92 59 -192 59q-74 0 -113 -36q-46 -42 -21 -108t106 -117q92 -59 192 -59q74 0 113 36zM494 91q81 51 106 117t-21 108 q-39 36 -113 36q-100 0 -192 -59q-81 -51 -106 -117t21 -108q39 -36 113 -36q100 0 192 59zM672 704l96 -58v11q0 36 33 56l14 8l-79 47l-26 -26q-3 -3 -10 -11t-12 -12q-2 -2 -4 -3.5t-3 -2.5zM896 480l96 -32l736 576l-128 64l-768 -431v-113l-160 -96l9 -8q2 -2 7 -6 q4 -4 11 -12t11 -12l26 -26zM1600 64l128 64l-520 408l-177 -138q-2 -3 -13 -7z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1696 1152q40 0 68 -28t28 -68v-1216q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v288h-544q-40 0 -68 28t-28 68v672q0 40 20 88t48 76l408 408q28 28 76 48t88 20h416q40 0 68 -28t28 -68v-328q68 40 128 40h416zM1152 939l-299 -299h299v299zM512 1323l-299 -299 h299v299zM708 676l316 316v416h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h512v256q0 40 20 88t48 76zM1664 -128v1152h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h896z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1404 151q0 -117 -79 -196t-196 -79q-135 0 -235 100l-777 776q-113 115 -113 271q0 159 110 270t269 111q158 0 273 -113l605 -606q10 -10 10 -22q0 -16 -30.5 -46.5t-46.5 -30.5q-13 0 -23 10l-606 607q-79 77 -181 77q-106 0 -179 -75t-73 -181q0 -105 76 -181 l776 -777q63 -63 145 -63q64 0 106 42t42 106q0 82 -63 145l-581 581q-26 24 -60 24q-29 0 -48 -19t-19 -48q0 -32 25 -59l410 -410q10 -10 10 -22q0 -16 -31 -47t-47 -31q-12 0 -22 10l-410 410q-63 61 -63 149q0 82 57 139t139 57q88 0 149 -63l581 -581q100 -98 100 -235 z" /> -<glyph unicode="" d="M384 0h768v384h-768v-384zM1280 0h128v896q0 14 -10 38.5t-20 34.5l-281 281q-10 10 -34 20t-39 10v-416q0 -40 -28 -68t-68 -28h-576q-40 0 -68 28t-28 68v416h-128v-1280h128v416q0 40 28 68t68 28h832q40 0 68 -28t28 -68v-416zM896 928v320q0 13 -9.5 22.5t-22.5 9.5 h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1536 896v-928q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h928q40 0 88 -20t76 -48l280 -280q28 -28 48 -76t20 -88z" /> -<glyph unicode="" d="M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M1536 192v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 704v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 1216v-128q0 -26 -19 -45 t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M384 128q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 640q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1152q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z M1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M381 -84q0 -80 -54.5 -126t-135.5 -46q-106 0 -172 66l57 88q49 -45 106 -45q29 0 50.5 14.5t21.5 42.5q0 64 -105 56l-26 56q8 10 32.5 43.5t42.5 54t37 38.5v1q-16 0 -48.5 -1t-48.5 -1v-53h-106v152h333v-88l-95 -115q51 -12 81 -49t30 -88zM383 543v-159h-362 q-6 36 -6 54q0 51 23.5 93t56.5 68t66 47.5t56.5 43.5t23.5 45q0 25 -14.5 38.5t-39.5 13.5q-46 0 -81 -58l-85 59q24 51 71.5 79.5t105.5 28.5q73 0 123 -41.5t50 -112.5q0 -50 -34 -91.5t-75 -64.5t-75.5 -50.5t-35.5 -52.5h127v60h105zM1792 224v-192q0 -13 -9.5 -22.5 t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1123v-99h-335v99h107q0 41 0.5 122t0.5 121v12h-2q-8 -17 -50 -54l-71 76l136 127h106v-404h108zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5 t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1760 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1728q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h1728zM483 704q-28 35 -51 80q-48 97 -48 188q0 181 134 309q133 127 393 127q50 0 167 -19q66 -12 177 -48q10 -38 21 -118q14 -123 14 -183q0 -18 -5 -45l-12 -3l-84 6 l-14 2q-50 149 -103 205q-88 91 -210 91q-114 0 -182 -59q-67 -58 -67 -146q0 -73 66 -140t279 -129q69 -20 173 -66q58 -28 95 -52h-743zM990 448h411q7 -39 7 -92q0 -111 -41 -212q-23 -55 -71 -104q-37 -35 -109 -81q-80 -48 -153 -66q-80 -21 -203 -21q-114 0 -195 23 l-140 40q-57 16 -72 28q-8 8 -8 22v13q0 108 -2 156q-1 30 0 68l2 37v44l102 2q15 -34 30 -71t22.5 -56t12.5 -27q35 -57 80 -94q43 -36 105 -57q59 -22 132 -22q64 0 139 27q77 26 122 86q47 61 47 129q0 84 -81 157q-34 29 -137 71z" /> -<glyph unicode="" d="M48 1313q-37 2 -45 4l-3 88q13 1 40 1q60 0 112 -4q132 -7 166 -7q86 0 168 3q116 4 146 5q56 0 86 2l-1 -14l2 -64v-9q-60 -9 -124 -9q-60 0 -79 -25q-13 -14 -13 -132q0 -13 0.5 -32.5t0.5 -25.5l1 -229l14 -280q6 -124 51 -202q35 -59 96 -92q88 -47 177 -47 q104 0 191 28q56 18 99 51q48 36 65 64q36 56 53 114q21 73 21 229q0 79 -3.5 128t-11 122.5t-13.5 159.5l-4 59q-5 67 -24 88q-34 35 -77 34l-100 -2l-14 3l2 86h84l205 -10q76 -3 196 10l18 -2q6 -38 6 -51q0 -7 -4 -31q-45 -12 -84 -13q-73 -11 -79 -17q-15 -15 -15 -41 q0 -7 1.5 -27t1.5 -31q8 -19 22 -396q6 -195 -15 -304q-15 -76 -41 -122q-38 -65 -112 -123q-75 -57 -182 -89q-109 -33 -255 -33q-167 0 -284 46q-119 47 -179 122q-61 76 -83 195q-16 80 -16 237v333q0 188 -17 213q-25 36 -147 39zM1536 -96v64q0 14 -9 23t-23 9h-1472 q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h1472q14 0 23 9t9 23z" /> -<glyph unicode="" horiz-adv-x="1664" d="M512 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23 v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 160v192 q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192 q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1664 1248v-1088q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1344q66 0 113 -47t47 -113 z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1190 955l293 293l-107 107l-293 -293zM1637 1248q0 -27 -18 -45l-1286 -1286q-18 -18 -45 -18t-45 18l-198 198q-18 18 -18 45t18 45l1286 1286q18 18 45 18t45 -18l198 -198q18 -18 18 -45zM286 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM636 1276 l196 -60l-196 -60l-60 -196l-60 196l-196 60l196 60l60 196zM1566 798l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM926 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98z" /> -<glyph unicode="" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" /> -<glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" /> -<glyph unicode="" d="M829 318q0 -76 -58.5 -112.5t-139.5 -36.5q-41 0 -80.5 9.5t-75.5 28.5t-58 53t-22 78q0 46 25 80t65.5 51.5t82 25t84.5 7.5q20 0 31 -2q2 -1 23 -16.5t26 -19t23 -18t24.5 -22t19 -22.5t17 -26t9 -26.5t4.5 -31.5zM755 863q0 -60 -33 -99.5t-92 -39.5q-53 0 -93 42.5 t-57.5 96.5t-17.5 106q0 61 32 104t92 43q53 0 93.5 -45t58 -101t17.5 -107zM861 1120l88 64h-265q-85 0 -161 -32t-127.5 -98t-51.5 -153q0 -93 64.5 -154.5t158.5 -61.5q22 0 43 3q-13 -29 -13 -54q0 -44 40 -94q-175 -12 -257 -63q-47 -29 -75.5 -73t-28.5 -95 q0 -43 18.5 -77.5t48.5 -56.5t69 -37t77.5 -21t76.5 -6q60 0 120.5 15.5t113.5 46t86 82.5t33 117q0 49 -20 89.5t-49 66.5t-58 47.5t-49 44t-20 44.5t15.5 42.5t37.5 39.5t44 42t37.5 59.5t15.5 82.5q0 60 -22.5 99.5t-72.5 90.5h83zM1152 672h128v64h-128v128h-64v-128 h-128v-64h128v-160h64v160zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M735 740q0 -36 32 -70.5t77.5 -68t90.5 -73.5t77 -104t32 -142q0 -90 -48 -173q-72 -122 -211 -179.5t-298 -57.5q-132 0 -246.5 41.5t-171.5 137.5q-37 60 -37 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 42 -47.5 74t-15.5 73q0 36 21 85q-46 -4 -68 -4 q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q77 66 182.5 98t217.5 32h418l-138 -88h-131q74 -63 112 -133t38 -160q0 -72 -24.5 -129.5t-59 -93t-69.5 -65t-59.5 -61.5t-24.5 -66zM589 836q38 0 78 16.5t66 43.5q53 57 53 159q0 58 -17 125t-48.5 129.5 t-84.5 103.5t-117 41q-42 0 -82.5 -19.5t-65.5 -52.5q-47 -59 -47 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26zM591 -37q58 0 111.5 13t99 39t73 73t27.5 109q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -48 2 q-53 0 -105 -7t-107.5 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -70 35 -123.5t91.5 -83t119 -44t127.5 -14.5zM1401 839h213v-108h-213v-219h-105v219h-212v108h212v217h105v-217z" /> -<glyph unicode="" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" /> -<glyph unicode="" horiz-adv-x="640" d="M640 1088v-896q0 -26 -19 -45t-45 -19t-45 19l-448 448q-19 19 -19 45t19 45l448 448q19 19 45 19t45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="640" d="M576 640q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19t-19 45v896q0 26 19 45t45 19t45 -19l448 -448q19 -19 19 -45z" /> -<glyph unicode="" horiz-adv-x="1664" d="M160 0h608v1152h-640v-1120q0 -13 9.5 -22.5t22.5 -9.5zM1536 32v1120h-640v-1152h608q13 0 22.5 9.5t9.5 22.5zM1664 1248v-1216q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1344q66 0 113 -47t47 -113z" /> -<glyph unicode="" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45zM1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" /> -<glyph unicode="" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 826v-794q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v794q44 -49 101 -87q362 -246 497 -345q57 -42 92.5 -65.5t94.5 -48t110 -24.5h1h1q51 0 110 24.5t94.5 48t92.5 65.5q170 123 498 345q57 39 100 87zM1792 1120q0 -79 -49 -151t-122 -123 q-376 -261 -468 -325q-10 -7 -42.5 -30.5t-54 -38t-52 -32.5t-57.5 -27t-50 -9h-1h-1q-23 0 -50 9t-57.5 27t-52 32.5t-54 38t-42.5 30.5q-91 64 -262 182.5t-205 142.5q-62 42 -117 115.5t-55 136.5q0 78 41.5 130t118.5 52h1472q65 0 112.5 -47t47.5 -113z" /> -<glyph unicode="" d="M349 911v-991h-330v991h330zM370 1217q1 -73 -50.5 -122t-135.5 -49h-2q-82 0 -132 49t-50 122q0 74 51.5 122.5t134.5 48.5t133 -48.5t51 -122.5zM1536 488v-568h-329v530q0 105 -40.5 164.5t-126.5 59.5q-63 0 -105.5 -34.5t-63.5 -85.5q-11 -30 -11 -81v-553h-329 q2 399 2 647t-1 296l-1 48h329v-144h-2q20 32 41 56t56.5 52t87 43.5t114.5 15.5q171 0 275 -113.5t104 -332.5z" /> -<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1771 0q0 -53 -37 -90l-107 -108q-39 -37 -91 -37q-53 0 -90 37l-363 364q-38 36 -38 90q0 53 43 96l-256 256l-126 -126q-14 -14 -34 -14t-34 14q2 -2 12.5 -12t12.5 -13t10 -11.5t10 -13.5t6 -13.5t5.5 -16.5t1.5 -18q0 -38 -28 -68q-3 -3 -16.5 -18t-19 -20.5 t-18.5 -16.5t-22 -15.5t-22 -9t-26 -4.5q-40 0 -68 28l-408 408q-28 28 -28 68q0 13 4.5 26t9 22t15.5 22t16.5 18.5t20.5 19t18 16.5q30 28 68 28q10 0 18 -1.5t16.5 -5.5t13.5 -6t13.5 -10t11.5 -10t13 -12.5t12 -12.5q-14 14 -14 34t14 34l348 348q14 14 34 14t34 -14 q-2 2 -12.5 12t-12.5 13t-10 11.5t-10 13.5t-6 13.5t-5.5 16.5t-1.5 18q0 38 28 68q3 3 16.5 18t19 20.5t18.5 16.5t22 15.5t22 9t26 4.5q40 0 68 -28l408 -408q28 -28 28 -68q0 -13 -4.5 -26t-9 -22t-15.5 -22t-16.5 -18.5t-20.5 -19t-18 -16.5q-30 -28 -68 -28 q-10 0 -18 1.5t-16.5 5.5t-13.5 6t-13.5 10t-11.5 10t-13 12.5t-12 12.5q14 -14 14 -34t-14 -34l-126 -126l256 -256q43 43 96 43q52 0 91 -37l363 -363q37 -39 37 -91z" /> -<glyph unicode="" horiz-adv-x="1792" d="M384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM576 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1004 351l101 382q6 26 -7.5 48.5t-38.5 29.5 t-48 -6.5t-30 -39.5l-101 -382q-60 -5 -107 -43.5t-63 -98.5q-20 -77 20 -146t117 -89t146 20t89 117q16 60 -6 117t-72 91zM1664 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 1024q0 53 -37.5 90.5 t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1472 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 384q0 -261 -141 -483q-19 -29 -54 -29h-1402q-35 0 -54 29 q-141 221 -141 483q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" /> -<glyph unicode="" horiz-adv-x="1792" d="M896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640 q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 174 120 321.5 t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M704 1152q-153 0 -286 -52t-211.5 -141t-78.5 -191q0 -82 53 -158t149 -132l97 -56l-35 -84q34 20 62 39l44 31l53 -10q78 -14 153 -14q153 0 286 52t211.5 141t78.5 191t-78.5 191t-211.5 141t-286 52zM704 1280q191 0 353.5 -68.5t256.5 -186.5t94 -257t-94 -257 t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224q0 139 94 257t256.5 186.5 t353.5 68.5zM1526 111q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129 q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5z" /> -<glyph unicode="" horiz-adv-x="896" d="M885 970q18 -20 7 -44l-540 -1157q-13 -25 -42 -25q-4 0 -14 2q-17 5 -25.5 19t-4.5 30l197 808l-406 -101q-4 -1 -12 -1q-18 0 -31 11q-18 15 -13 39l201 825q4 14 16 23t28 9h328q19 0 32 -12.5t13 -29.5q0 -8 -5 -18l-171 -463l396 98q8 2 12 2q19 0 34 -15z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 288v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192q0 52 38 90t90 38h512v192h-96q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-96v-192h512q52 0 90 -38t38 -90v-192h96q40 0 68 -28t28 -68 z" /> -<glyph unicode="" horiz-adv-x="1664" d="M896 708v-580q0 -104 -76 -180t-180 -76t-180 76t-76 180q0 26 19 45t45 19t45 -19t19 -45q0 -50 39 -89t89 -39t89 39t39 89v580q33 11 64 11t64 -11zM1664 681q0 -13 -9.5 -22.5t-22.5 -9.5q-11 0 -23 10q-49 46 -93 69t-102 23q-68 0 -128 -37t-103 -97 q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -28 -17q-18 0 -29 17q-4 6 -14.5 24t-17.5 28q-43 60 -102.5 97t-127.5 37t-127.5 -37t-102.5 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -29 -17q-17 0 -28 17q-4 6 -14.5 24t-17.5 28q-43 60 -103 97t-128 37q-58 0 -102 -23t-93 -69 q-12 -10 -23 -10q-13 0 -22.5 9.5t-9.5 22.5q0 5 1 7q45 183 172.5 319.5t298 204.5t360.5 68q140 0 274.5 -40t246.5 -113.5t194.5 -187t115.5 -251.5q1 -2 1 -7zM896 1408v-98q-42 2 -64 2t-64 -2v98q0 26 19 45t45 19t45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M768 -128h896v640h-416q-40 0 -68 28t-28 68v416h-384v-1152zM1024 1312v64q0 13 -9.5 22.5t-22.5 9.5h-704q-13 0 -22.5 -9.5t-9.5 -22.5v-64q0 -13 9.5 -22.5t22.5 -9.5h704q13 0 22.5 9.5t9.5 22.5zM1280 640h299l-299 299v-299zM1792 512v-672q0 -40 -28 -68t-68 -28 h-960q-40 0 -68 28t-28 68v160h-544q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1088q40 0 68 -28t28 -68v-328q21 -13 36 -28l408 -408q28 -28 48 -76t20 -88z" /> -<glyph unicode="" horiz-adv-x="1024" d="M736 960q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5q0 46 -54 71t-106 25q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5q50 0 99.5 -16t87 -54t37.5 -90zM896 960q0 72 -34.5 134t-90 101.5t-123 62t-136.5 22.5t-136.5 -22.5t-123 -62t-90 -101.5t-34.5 -134 q0 -101 68 -180q10 -11 30.5 -33t30.5 -33q128 -153 141 -298h228q13 145 141 298q10 11 30.5 33t30.5 33q68 79 68 180zM1024 960q0 -155 -103 -268q-45 -49 -74.5 -87t-59.5 -95.5t-34 -107.5q47 -28 47 -82q0 -37 -25 -64q25 -27 25 -64q0 -52 -45 -81q13 -23 13 -47 q0 -46 -31.5 -71t-77.5 -25q-20 -44 -60 -70t-87 -26t-87 26t-60 70q-46 0 -77.5 25t-31.5 71q0 24 13 47q-45 29 -45 81q0 37 25 64q-25 27 -25 64q0 54 47 82q-4 50 -34 107.5t-59.5 95.5t-74.5 87q-103 113 -103 268q0 99 44.5 184.5t117 142t164 89t186.5 32.5 t186.5 -32.5t164 -89t117 -142t44.5 -184.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 352v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5q-12 0 -24 10l-319 320q-9 9 -9 22q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h1376q13 0 22.5 -9.5t9.5 -22.5zM1792 896q0 -14 -9 -23l-320 -320q-9 -9 -23 -9 q-13 0 -22.5 9.5t-9.5 22.5v192h-1376q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1376v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1280 608q0 14 -9 23t-23 9h-224v352q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-352h-224q-13 0 -22.5 -9.5t-9.5 -22.5q0 -14 9 -23l352 -352q9 -9 23 -9t23 9l351 351q10 12 10 24zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1280 672q0 14 -9 23l-352 352q-9 9 -23 9t-23 -9l-351 -351q-10 -12 -10 -24q0 -14 9 -23t23 -9h224v-352q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v352h224q13 0 22.5 9.5t9.5 22.5zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M384 192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56 t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68 t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5t81 -103t47.5 -132.5t24 -138t5.5 -131zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1280 832q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 832q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5t-131.5 271.5v132q-164 20 -274 128t-110 252v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48 t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252 t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136z" /> -<glyph unicode="" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM288 1152v-1280h-64q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h64zM1408 1152v-1280h-1024v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128zM1792 928v-832q0 -92 -66 -158t-158 -66h-64v1280h64q92 0 158 -66 t66 -158z" /> -<glyph unicode="" horiz-adv-x="1792" d="M912 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1728 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q50 42 91 88t85 119.5t74.5 158.5 t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q190 -28 307 -158.5t117 -282.5q0 -139 19.5 -260t50 -206t74.5 -158.5t85 -119.5t91 -88z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1664 896q0 80 -56 136t-136 56h-64v-384h64q80 0 136 56t56 136zM0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM1856 896q0 -159 -112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158v736q0 26 19 45 t45 19h1152q159 0 271.5 -112.5t112.5 -271.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M640 1472v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45 t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45zM1408 1472v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45z" /> -<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M384 736q0 14 9 23t23 9h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64zM1120 512q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704zM1120 256q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704 q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704z" /> -<glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1536h-1152v-1536h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM1408 1472v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1152h-256v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM896 1056v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5 t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1408 1088v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1280q0 26 19 45t45 19h320 v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1920" d="M640 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM1536 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1664 800v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM1920 1344v-1152 q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128q-26 0 -45 19t-19 45t19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1280 416v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM640 1152h512v128h-512v-128zM256 1152v-1280h-32 q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h32zM1440 1152v-1280h-1088v1280h160v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h160zM1792 928v-832q0 -92 -66 -158t-158 -66h-32v1280h32q92 0 158 -66t66 -158z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1920 576q-1 -32 -288 -96l-352 -32l-224 -64h-64l-293 -352h69q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-96h-160h-64v32h64v416h-160l-192 -224h-96l-32 32v192h32v32h128v8l-192 24v128l192 24v8h-128v32h-32v192l32 32h96l192 -224h160v416h-64v32h64h160h96 q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-69l293 -352h64l224 -64l352 -32q261 -58 287 -93z" /> -<glyph unicode="" horiz-adv-x="1664" d="M640 640v384h-256v-256q0 -53 37.5 -90.5t90.5 -37.5h128zM1664 192v-192h-1152v192l128 192h-128q-159 0 -271.5 112.5t-112.5 271.5v320l-64 64l32 128h480l32 128h960l32 -192l-64 -32v-800z" /> -<glyph unicode="" d="M1280 192v896q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-512v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-896q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h512v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1024" d="M627 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23zM1011 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23z" /> -<glyph unicode="" horiz-adv-x="1024" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM979 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23 l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" /> -<glyph unicode="" horiz-adv-x="1152" d="M1075 224q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM1075 608q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393 q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" /> -<glyph unicode="" horiz-adv-x="1152" d="M1075 672q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23zM1075 1056q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" /> -<glyph unicode="" horiz-adv-x="640" d="M627 992q0 -13 -10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" /> -<glyph unicode="" horiz-adv-x="640" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" /> -<glyph unicode="" horiz-adv-x="1152" d="M1075 352q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" /> -<glyph unicode="" horiz-adv-x="1152" d="M1075 800q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1792 544v832q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1376v-1088q0 -66 -47 -113t-113 -47h-544q0 -37 16 -77.5t32 -71t16 -43.5q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19 t-19 45q0 14 16 44t32 70t16 78h-544q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" /> -<glyph unicode="" horiz-adv-x="1920" d="M416 256q-66 0 -113 47t-47 113v704q0 66 47 113t113 47h1088q66 0 113 -47t47 -113v-704q0 -66 -47 -113t-113 -47h-1088zM384 1120v-704q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5z M1760 192h160v-96q0 -40 -47 -68t-113 -28h-1600q-66 0 -113 28t-47 68v96h160h1600zM1040 96q16 0 16 16t-16 16h-160q-16 0 -16 -16t16 -16h160z" /> -<glyph unicode="" horiz-adv-x="1152" d="M640 128q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1024 288v960q0 13 -9.5 22.5t-22.5 9.5h-832q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h832q13 0 22.5 9.5t9.5 22.5zM1152 1248v-1088q0 -66 -47 -113t-113 -47h-832 q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h832q66 0 113 -47t47 -113z" /> -<glyph unicode="" horiz-adv-x="768" d="M464 128q0 33 -23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5t56.5 23.5t23.5 56.5zM672 288v704q0 13 -9.5 22.5t-22.5 9.5h-512q-13 0 -22.5 -9.5t-9.5 -22.5v-704q0 -13 9.5 -22.5t22.5 -9.5h512q13 0 22.5 9.5t9.5 22.5zM480 1136 q0 16 -16 16h-160q-16 0 -16 -16t16 -16h160q16 0 16 16zM768 1152v-1024q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v1024q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" /> -<glyph unicode="" d="M768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103 t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" /> -<glyph unicode="" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" /> -<glyph unicode="" horiz-adv-x="1568" d="M496 192q0 -60 -42.5 -102t-101.5 -42q-60 0 -102 42t-42 102t42 102t102 42q59 0 101.5 -42t42.5 -102zM928 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -66 -47 -113t-113 -47t-113 47t-47 113 t47 113t113 47t113 -47t47 -113zM1360 192q0 -46 -33 -79t-79 -33t-79 33t-33 79t33 79t79 33t79 -33t33 -79zM528 1088q0 -73 -51.5 -124.5t-124.5 -51.5t-124.5 51.5t-51.5 124.5t51.5 124.5t124.5 51.5t124.5 -51.5t51.5 -124.5zM992 1280q0 -80 -56 -136t-136 -56 t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1536 640q0 -40 -28 -68t-68 -28t-68 28t-28 68t28 68t68 28t68 -28t28 -68zM1328 1088q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5z" /> -<glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" /> -<glyph unicode="" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1536 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-960q0 -40 28 -68t68 -28h1216q40 0 68 28t28 68zM1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320 q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1781 605q0 35 -53 35h-1088q-40 0 -85.5 -21.5t-71.5 -52.5l-294 -363q-18 -24 -18 -40q0 -35 53 -35h1088q40 0 86 22t71 53l294 363q18 22 18 39zM640 768h768v160q0 40 -28 68t-68 28h-576q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68 v-853l256 315q44 53 116 87.5t140 34.5zM1909 605q0 -62 -46 -120l-295 -363q-43 -53 -116 -87.5t-140 -34.5h-1088q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h192q54 0 99 -24.5t67 -70.5q15 -32 15 -68z " /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" d="M1134 461q-37 -121 -138 -195t-228 -74t-228 74t-138 195q-8 25 4 48.5t38 31.5q25 8 48.5 -4t31.5 -38q25 -80 92.5 -129.5t151.5 -49.5t151.5 49.5t92.5 129.5q8 26 32 38t49 4t37 -31.5t4 -48.5zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5 t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1134 307q8 -25 -4 -48.5t-37 -31.5t-49 4t-32 38q-25 80 -92.5 129.5t-151.5 49.5t-151.5 -49.5t-92.5 -129.5q-8 -26 -31.5 -38t-48.5 -4q-26 8 -38 31.5t-4 48.5q37 121 138 195t228 74t228 -74t138 -195zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204 t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1152 448q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h640q26 0 45 -19t19 -45zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1920" d="M832 448v128q0 14 -9 23t-23 9h-192v192q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-192h-192q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h192v-192q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v192h192q14 0 23 9t9 23zM1408 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1920 512q0 -212 -150 -362t-362 -150q-192 0 -338 128h-220q-146 -128 -338 -128q-212 0 -362 150 t-150 362t150 362t362 150h896q212 0 362 -150t150 -362z" /> -<glyph unicode="" horiz-adv-x="1920" d="M384 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM512 624v-96q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h224q16 0 16 -16zM384 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 368v-96q0 -16 -16 -16 h-864q-16 0 -16 16v96q0 16 16 16h864q16 0 16 -16zM768 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM640 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1024 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16 h96q16 0 16 -16zM896 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1280 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1152 880v-96 q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 880v-352q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h112v240q0 16 16 16h96q16 0 16 -16zM1792 128v896h-1664v-896 h1664zM1920 1024v-896q0 -53 -37.5 -90.5t-90.5 -37.5h-1664q-53 0 -90.5 37.5t-37.5 90.5v896q0 53 37.5 90.5t90.5 37.5h1664q53 0 90.5 -37.5t37.5 -90.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1664 491v616q-169 -91 -306 -91q-82 0 -145 32q-100 49 -184 76.5t-178 27.5q-173 0 -403 -127v-599q245 113 433 113q55 0 103.5 -7.5t98 -26t77 -31t82.5 -39.5l28 -14q44 -22 101 -22q120 0 293 92zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9 h-64q-14 0 -23 9t-9 23v1266q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102 q-15 -9 -33 -9q-16 0 -32 8q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" /> -<glyph unicode="" horiz-adv-x="1792" d="M832 536v192q-181 -16 -384 -117v-185q205 96 384 110zM832 954v197q-172 -8 -384 -126v-189q215 111 384 118zM1664 491v184q-235 -116 -384 -71v224q-20 6 -39 15q-5 3 -33 17t-34.5 17t-31.5 15t-34.5 15.5t-32.5 13t-36 12.5t-35 8.5t-39.5 7.5t-39.5 4t-44 2 q-23 0 -49 -3v-222h19q102 0 192.5 -29t197.5 -82q19 -9 39 -15v-188q42 -17 91 -17q120 0 293 92zM1664 918v189q-169 -91 -306 -91q-45 0 -78 8v-196q148 -42 384 90zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v1266 q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102q-15 -9 -33 -9q-16 0 -32 8 q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" /> -<glyph unicode="" horiz-adv-x="1664" d="M585 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23zM1664 96v-64q0 -14 -9 -23t-23 -9h-960q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h960q14 0 23 -9 t9 -23z" /> -<glyph unicode="" horiz-adv-x="1920" d="M617 137l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23zM1208 1204l-373 -1291q-4 -13 -15.5 -19.5t-23.5 -2.5l-62 17q-13 4 -19.5 15.5t-2.5 24.5 l373 1291q4 13 15.5 19.5t23.5 2.5l62 -17q13 -4 19.5 -15.5t2.5 -24.5zM1865 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23z" /> -<glyph unicode="" horiz-adv-x="1792" d="M640 454v-70q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-69l-397 -398q-19 -19 -19 -45t19 -45zM1792 416q0 -58 -17 -133.5t-38.5 -138t-48 -125t-40.5 -90.5l-20 -40q-8 -17 -28 -17q-6 0 -9 1 q-25 8 -23 34q43 400 -106 565q-64 71 -170.5 110.5t-267.5 52.5v-251q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-262q411 -28 599 -221q169 -173 169 -509z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1186 579l257 250l-356 52l-66 10l-30 60l-159 322v-963l59 -31l318 -168l-60 355l-12 66zM1638 841l-363 -354l86 -500q5 -33 -6 -51.5t-34 -18.5q-17 0 -40 12l-449 236l-449 -236q-23 -12 -40 -12q-23 0 -34 18.5t-6 51.5l86 500l-364 354q-32 32 -23 59.5t54 34.5 l502 73l225 455q20 41 49 41q28 0 49 -41l225 -455l502 -73q45 -7 54 -34.5t-24 -59.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1401 1187l-640 -1280q-17 -35 -57 -35q-5 0 -15 2q-22 5 -35.5 22.5t-13.5 39.5v576h-576q-22 0 -39.5 13.5t-22.5 35.5t4 42t29 30l1280 640q13 7 29 7q27 0 45 -19q15 -14 18.5 -34.5t-6.5 -39.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M557 256h595v595zM512 301l595 595h-595v-595zM1664 224v-192q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v224h-864q-14 0 -23 9t-9 23v864h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224v224q0 14 9 23t23 9h192q14 0 23 -9t9 -23 v-224h851l246 247q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-247 -246v-851h224q14 0 23 -9t9 -23z" /> -<glyph unicode="" horiz-adv-x="1024" d="M288 64q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM288 1216q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM928 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1024 1088q0 -52 -26 -96.5t-70 -69.5 q-2 -287 -226 -414q-68 -38 -203 -81q-128 -40 -169.5 -71t-41.5 -100v-26q44 -25 70 -69.5t26 -96.5q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 52 26 96.5t70 69.5v820q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136q0 -52 -26 -96.5t-70 -69.5v-497 q54 26 154 57q55 17 87.5 29.5t70.5 31t59 39.5t40.5 51t28 69.5t8.5 91.5q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136z" /> -<glyph unicode="" horiz-adv-x="1664" d="M439 265l-256 -256q-10 -9 -23 -9q-12 0 -23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23zM608 224v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM384 448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23t9 23t23 9h320 q14 0 23 -9t9 -23zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-334 335q-21 21 -42 56l239 18l273 -274q27 -27 68 -27.5t68 26.5l147 146q28 28 28 67q0 40 -28 68l-274 275l18 239q35 -21 56 -42l336 -336q84 -86 84 -204zM1031 1044l-239 -18 l-273 274q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l274 -274l-18 -240q-35 21 -56 42l-336 336q-84 86 -84 204q0 120 85 203l147 146q83 83 203 83q121 0 204 -85l334 -335q21 -21 42 -56zM1664 960q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9 t-9 23t9 23t23 9h320q14 0 23 -9t9 -23zM1120 1504v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM1527 1353l-256 -256q-11 -9 -23 -9t-23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" /> -<glyph unicode="" horiz-adv-x="1024" d="M704 280v-240q0 -16 -12 -28t-28 -12h-240q-16 0 -28 12t-12 28v240q0 16 12 28t28 12h240q16 0 28 -12t12 -28zM1020 880q0 -54 -15.5 -101t-35 -76.5t-55 -59.5t-57.5 -43.5t-61 -35.5q-41 -23 -68.5 -65t-27.5 -67q0 -17 -12 -32.5t-28 -15.5h-240q-15 0 -25.5 18.5 t-10.5 37.5v45q0 83 65 156.5t143 108.5q59 27 84 56t25 76q0 42 -46.5 74t-107.5 32q-65 0 -108 -29q-35 -25 -107 -115q-13 -16 -31 -16q-12 0 -25 8l-164 125q-13 10 -15.5 25t5.5 28q160 266 464 266q80 0 161 -31t146 -83t106 -127.5t41 -158.5z" /> -<glyph unicode="" horiz-adv-x="640" d="M640 192v-128q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64v384h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-576h64q26 0 45 -19t19 -45zM512 1344v-192q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v192 q0 26 19 45t45 19h256q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="640" d="M512 288v-224q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v224q0 26 19 45t45 19h256q26 0 45 -19t19 -45zM542 1344l-28 -768q-1 -26 -20.5 -45t-45.5 -19h-256q-26 0 -45.5 19t-20.5 45l-28 768q-1 26 17.5 45t44.5 19h320q26 0 44.5 -19t17.5 -45z" /> -<glyph unicode="" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1534 846v-206h-514l-3 27 q-4 28 -4 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q83 65 188 65q110 0 178 -59.5t68 -158.5q0 -56 -24.5 -103t-62 -76.5t-81.5 -58.5t-82 -50.5t-65.5 -51.5t-30.5 -63h232v80 h126z" /> -<glyph unicode="" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1536 -50v-206h-514l-4 27 q-3 45 -3 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q80 65 188 65q110 0 178 -59.5t68 -158.5q0 -66 -34.5 -118.5t-84 -86t-99.5 -62.5t-87 -63t-41 -73h232v80h126z" /> -<glyph unicode="" horiz-adv-x="1920" d="M896 128l336 384h-768l-336 -384h768zM1909 1205q15 -34 9.5 -71.5t-30.5 -65.5l-896 -1024q-38 -44 -96 -44h-768q-38 0 -69.5 20.5t-47.5 54.5q-15 34 -9.5 71.5t30.5 65.5l896 1024q38 44 96 44h768q38 0 69.5 -20.5t47.5 -54.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1664 438q0 -81 -44.5 -135t-123.5 -54q-41 0 -77.5 17.5t-59 38t-56.5 38t-71 17.5q-110 0 -110 -124q0 -39 16 -115t15 -115v-5q-22 0 -33 -1q-34 -3 -97.5 -11.5t-115.5 -13.5t-98 -5q-61 0 -103 26.5t-42 83.5q0 37 17.5 71t38 56.5t38 59t17.5 77.5q0 79 -54 123.5 t-135 44.5q-84 0 -143 -45.5t-59 -127.5q0 -43 15 -83t33.5 -64.5t33.5 -53t15 -50.5q0 -45 -46 -89q-37 -35 -117 -35q-95 0 -245 24q-9 2 -27.5 4t-27.5 4l-13 2q-1 0 -3 1q-2 0 -2 1v1024q2 -1 17.5 -3.5t34 -5t21.5 -3.5q150 -24 245 -24q80 0 117 35q46 44 46 89 q0 22 -15 50.5t-33.5 53t-33.5 64.5t-15 83q0 82 59 127.5t144 45.5q80 0 134 -44.5t54 -123.5q0 -41 -17.5 -77.5t-38 -59t-38 -56.5t-17.5 -71q0 -57 42 -83.5t103 -26.5q64 0 180 15t163 17v-2q-1 -2 -3.5 -17.5t-5 -34t-3.5 -21.5q-24 -150 -24 -245q0 -80 35 -117 q44 -46 89 -46q22 0 50.5 15t53 33.5t64.5 33.5t83 15q82 0 127.5 -59t45.5 -143z" /> -<glyph unicode="" horiz-adv-x="1152" d="M1152 832v-128q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-217 24 -364.5 187.5t-147.5 384.5v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -185 131.5 -316.5t316.5 -131.5 t316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45zM896 1216v-512q0 -132 -94 -226t-226 -94t-226 94t-94 226v512q0 132 94 226t226 94t226 -94t94 -226z" /> -<glyph unicode="" horiz-adv-x="1408" d="M271 591l-101 -101q-42 103 -42 214v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -53 15 -113zM1385 1193l-361 -361v-128q0 -132 -94 -226t-226 -94q-55 0 -109 19l-96 -96q97 -51 205 -51q185 0 316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45v-128 q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-125 13 -235 81l-254 -254q-10 -10 -23 -10t-23 10l-82 82q-10 10 -10 23t10 23l1234 1234q10 10 23 10t23 -10l82 -82q10 -10 10 -23 t-10 -23zM1005 1325l-621 -621v512q0 132 94 226t226 94q102 0 184.5 -59t116.5 -152z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1088 576v640h-448v-1137q119 63 213 137q235 184 235 360zM1280 1344v-768q0 -86 -33.5 -170.5t-83 -150t-118 -127.5t-126.5 -103t-121 -77.5t-89.5 -49.5t-42.5 -20q-12 -6 -26 -6t-26 6q-16 7 -42.5 20t-89.5 49.5t-121 77.5t-126.5 103t-118 127.5t-83 150 t-33.5 170.5v768q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1664" d="M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" /> -<glyph unicode="" horiz-adv-x="1408" d="M512 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 1376v-320q0 -16 -12 -25q-8 -7 -20 -7q-4 0 -7 1l-448 96q-11 2 -18 11t-7 20h-256v-102q111 -23 183.5 -111t72.5 -203v-800q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v800 q0 106 62.5 190.5t161.5 114.5v111h-32q-59 0 -115 -23.5t-91.5 -53t-66 -66.5t-40.5 -53.5t-14 -24.5q-17 -35 -57 -35q-16 0 -29 7q-23 12 -31.5 37t3.5 49q5 10 14.5 26t37.5 53.5t60.5 70t85 67t108.5 52.5q-25 42 -25 86q0 66 47 113t113 47t113 -47t47 -113 q0 -33 -14 -64h302q0 11 7 20t18 11l448 96q3 1 7 1q12 0 20 -7q12 -9 12 -25z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1440 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1664 1376q0 -249 -75.5 -430.5t-253.5 -360.5q-81 -80 -195 -176l-20 -379q-2 -16 -16 -26l-384 -224q-7 -4 -16 -4q-12 0 -23 9l-64 64q-13 14 -8 32l85 276l-281 281l-276 -85q-3 -1 -9 -1 q-14 0 -23 9l-64 64q-17 19 -5 39l224 384q10 14 26 16l379 20q96 114 176 195q188 187 358 258t431 71q14 0 24 -9.5t10 -22.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1745 763l-164 -763h-334l178 832q13 56 -15 88q-27 33 -83 33h-169l-204 -953h-334l204 953h-286l-204 -953h-334l204 953l-153 327h1276q101 0 189.5 -40.5t147.5 -113.5q60 -73 81 -168.5t0 -194.5z" /> -<glyph unicode="" d="M909 141l102 102q19 19 19 45t-19 45l-307 307l307 307q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M717 141l454 454q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l307 -307l-307 -307q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1165 397l102 102q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l307 307l307 -307q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M813 237l454 454q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-307 -307l-307 307q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1130 939l16 175h-884l47 -534h612l-22 -228l-197 -53l-196 53l-13 140h-175l22 -278l362 -100h4v1l359 99l50 544h-644l-15 181h674zM0 1408h1408l-128 -1438l-578 -162l-574 162z" /> -<glyph unicode="" horiz-adv-x="1792" d="M275 1408h1505l-266 -1333l-804 -267l-698 267l71 356h297l-29 -147l422 -161l486 161l68 339h-1208l58 297h1209l38 191h-1208z" /> -<glyph unicode="" horiz-adv-x="1792" d="M960 1280q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1792 352v-352q0 -22 -20 -30q-8 -2 -12 -2q-13 0 -23 9l-93 93q-119 -143 -318.5 -226.5t-429.5 -83.5t-429.5 83.5t-318.5 226.5l-93 -93q-9 -9 -23 -9q-4 0 -12 2q-20 8 -20 30v352 q0 14 9 23t23 9h352q22 0 30 -20q8 -19 -7 -35l-100 -100q67 -91 189.5 -153.5t271.5 -82.5v647h-192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h192v163q-58 34 -93 92.5t-35 128.5q0 106 75 181t181 75t181 -75t75 -181q0 -70 -35 -128.5t-93 -92.5v-163h192q26 0 45 -19 t19 -45v-128q0 -26 -19 -45t-45 -19h-192v-647q149 20 271.5 82.5t189.5 153.5l-100 100q-15 16 -7 35q8 20 30 20h352q14 0 23 -9t9 -23z" /> -<glyph unicode="" horiz-adv-x="1152" d="M1056 768q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v320q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45q0 106 -75 181t-181 75t-181 -75t-75 -181 v-320h736z" /> -<glyph unicode="" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM1152 640q0 159 -112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM1280 640q0 -212 -150 -362t-362 -150t-362 150 t-150 362t150 362t362 150t362 -150t150 -362zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM896 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM1408 800v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" /> -<glyph unicode="" horiz-adv-x="384" d="M384 288v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 1312v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" /> -<glyph unicode="" d="M512 256q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM863 162q-13 232 -177 396t-396 177q-14 1 -24 -9t-10 -23v-128q0 -13 8.5 -22t21.5 -10q154 -11 264 -121t121 -264q1 -13 10 -21.5t22 -8.5h128q13 0 23 10 t9 24zM1247 161q-5 154 -56 297.5t-139.5 260t-205 205t-260 139.5t-297.5 56q-14 1 -23 -9q-10 -10 -10 -23v-128q0 -13 9 -22t22 -10q204 -7 378 -111.5t278.5 -278.5t111.5 -378q1 -13 10 -22t22 -9h128q13 0 23 10q11 9 9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1152 585q32 18 32 55t-32 55l-544 320q-31 19 -64 1q-32 -19 -32 -56v-640q0 -37 32 -56 q16 -8 32 -8q17 0 32 9z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1024 1084l316 -316l-572 -572l-316 316zM813 105l618 618q19 19 19 45t-19 45l-362 362q-18 18 -45 18t-45 -18l-618 -618q-19 -19 -19 -45t19 -45l362 -362q18 -18 45 -18t45 18zM1702 742l-907 -908q-37 -37 -90.5 -37t-90.5 37l-126 126q56 56 56 136t-56 136 t-136 56t-136 -56l-125 126q-37 37 -37 90.5t37 90.5l907 906q37 37 90.5 37t90.5 -37l125 -125q-56 -56 -56 -136t56 -136t136 -56t136 56l126 -125q37 -37 37 -90.5t-37 -90.5z" /> -<glyph unicode="" d="M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h832q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5 t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1024" d="M1018 933q-18 -37 -58 -37h-192v-864q0 -14 -9 -23t-23 -9h-704q-21 0 -29 18q-8 20 4 35l160 192q9 11 25 11h320v640h-192q-40 0 -58 37q-17 37 9 68l320 384q18 22 49 22t49 -22l320 -384q27 -32 9 -68z" /> -<glyph unicode="" horiz-adv-x="1024" d="M32 1280h704q13 0 22.5 -9.5t9.5 -23.5v-863h192q40 0 58 -37t-9 -69l-320 -384q-18 -22 -49 -22t-49 22l-320 384q-26 31 -9 69q18 37 58 37h192v640h-320q-14 0 -25 11l-160 192q-13 14 -4 34q9 19 29 19z" /> -<glyph unicode="" d="M685 237l614 614q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-467 -467l-211 211q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l358 -358q19 -19 45 -19t45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5 t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M404 428l152 -152l-52 -52h-56v96h-96v56zM818 818q14 -13 -3 -30l-291 -291q-17 -17 -30 -3q-14 13 3 30l291 291q17 17 30 3zM544 128l544 544l-288 288l-544 -544v-288h288zM1152 736l92 92q28 28 28 68t-28 68l-152 152q-28 28 -68 28t-68 -28l-92 -92zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M1280 608v480q0 26 -19 45t-45 19h-480q-42 0 -59 -39q-17 -41 14 -70l144 -144l-534 -534q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l534 534l144 -144q18 -19 45 -19q12 0 25 5q39 17 39 59zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M1005 435l352 352q19 19 19 45t-19 45l-352 352q-30 31 -69 14q-40 -17 -40 -59v-160q-119 0 -216 -19.5t-162.5 -51t-114 -79t-76.5 -95.5t-44.5 -109t-21.5 -111.5t-5 -110.5q0 -181 167 -404q10 -12 25 -12q7 0 13 3q22 9 19 33q-44 354 62 473q46 52 130 75.5 t224 23.5v-160q0 -42 40 -59q12 -5 24 -5q26 0 45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M640 448l256 128l-256 128v-256zM1024 1039v-542l-512 -256v542zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1145 861q18 -35 -5 -66l-320 -448q-19 -27 -52 -27t-52 27l-320 448q-23 31 -5 66q17 35 57 35h640q40 0 57 -35zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M1145 419q-17 -35 -57 -35h-640q-40 0 -57 35q-18 35 5 66l320 448q19 27 52 27t52 -27l320 -448q23 -31 5 -66zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M1088 640q0 -33 -27 -52l-448 -320q-31 -23 -66 -5q-35 17 -35 57v640q0 40 35 57q35 18 66 -5l448 -320q27 -19 27 -52zM1280 160v960q0 14 -9 23t-23 9h-960q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h960q14 0 23 9t9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1024" d="M976 229l35 -159q3 -12 -3 -22.5t-17 -14.5l-5 -1q-4 -2 -10.5 -3.5t-16 -4.5t-21.5 -5.5t-25.5 -5t-30 -5t-33.5 -4.5t-36.5 -3t-38.5 -1q-234 0 -409 130.5t-238 351.5h-95q-13 0 -22.5 9.5t-9.5 22.5v113q0 13 9.5 22.5t22.5 9.5h66q-2 57 1 105h-67q-14 0 -23 9 t-9 23v114q0 14 9 23t23 9h98q67 210 243.5 338t400.5 128q102 0 194 -23q11 -3 20 -15q6 -11 3 -24l-43 -159q-3 -13 -14 -19.5t-24 -2.5l-4 1q-4 1 -11.5 2.5l-17.5 3.5t-22.5 3.5t-26 3t-29 2.5t-29.5 1q-126 0 -226 -64t-150 -176h468q16 0 25 -12q10 -12 7 -26 l-24 -114q-5 -26 -32 -26h-488q-3 -37 0 -105h459q15 0 25 -12q9 -12 6 -27l-24 -112q-2 -11 -11 -18.5t-20 -7.5h-387q48 -117 149.5 -185.5t228.5 -68.5q18 0 36 1.5t33.5 3.5t29.5 4.5t24.5 5t18.5 4.5l12 3l5 2q13 5 26 -2q12 -7 15 -21z" /> -<glyph unicode="" horiz-adv-x="1024" d="M1020 399v-367q0 -14 -9 -23t-23 -9h-956q-14 0 -23 9t-9 23v150q0 13 9.5 22.5t22.5 9.5h97v383h-95q-14 0 -23 9.5t-9 22.5v131q0 14 9 23t23 9h95v223q0 171 123.5 282t314.5 111q185 0 335 -125q9 -8 10 -20.5t-7 -22.5l-103 -127q-9 -11 -22 -12q-13 -2 -23 7 q-5 5 -26 19t-69 32t-93 18q-85 0 -137 -47t-52 -123v-215h305q13 0 22.5 -9t9.5 -23v-131q0 -13 -9.5 -22.5t-22.5 -9.5h-305v-379h414v181q0 13 9 22.5t23 9.5h162q14 0 23 -9.5t9 -22.5z" /> -<glyph unicode="" horiz-adv-x="1024" d="M978 351q0 -153 -99.5 -263.5t-258.5 -136.5v-175q0 -14 -9 -23t-23 -9h-135q-13 0 -22.5 9.5t-9.5 22.5v175q-66 9 -127.5 31t-101.5 44.5t-74 48t-46.5 37.5t-17.5 18q-17 21 -2 41l103 135q7 10 23 12q15 2 24 -9l2 -2q113 -99 243 -125q37 -8 74 -8q81 0 142.5 43 t61.5 122q0 28 -15 53t-33.5 42t-58.5 37.5t-66 32t-80 32.5q-39 16 -61.5 25t-61.5 26.5t-62.5 31t-56.5 35.5t-53.5 42.5t-43.5 49t-35.5 58t-21 66.5t-8.5 78q0 138 98 242t255 134v180q0 13 9.5 22.5t22.5 9.5h135q14 0 23 -9t9 -23v-176q57 -6 110.5 -23t87 -33.5 t63.5 -37.5t39 -29t15 -14q17 -18 5 -38l-81 -146q-8 -15 -23 -16q-14 -3 -27 7q-3 3 -14.5 12t-39 26.5t-58.5 32t-74.5 26t-85.5 11.5q-95 0 -155 -43t-60 -111q0 -26 8.5 -48t29.5 -41.5t39.5 -33t56 -31t60.5 -27t70 -27.5q53 -20 81 -31.5t76 -35t75.5 -42.5t62 -50 t53 -63.5t31.5 -76.5t13 -94z" /> -<glyph unicode="" horiz-adv-x="898" d="M898 1066v-102q0 -14 -9 -23t-23 -9h-168q-23 -144 -129 -234t-276 -110q167 -178 459 -536q14 -16 4 -34q-8 -18 -29 -18h-195q-16 0 -25 12q-306 367 -498 571q-9 9 -9 22v127q0 13 9.5 22.5t22.5 9.5h112q132 0 212.5 43t102.5 125h-427q-14 0 -23 9t-9 23v102 q0 14 9 23t23 9h413q-57 113 -268 113h-145q-13 0 -22.5 9.5t-9.5 22.5v133q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-102q0 -14 -9 -23t-23 -9h-233q47 -61 64 -144h171q14 0 23 -9t9 -23z" /> -<glyph unicode="" horiz-adv-x="1027" d="M603 0h-172q-13 0 -22.5 9t-9.5 23v330h-288q-13 0 -22.5 9t-9.5 23v103q0 13 9.5 22.5t22.5 9.5h288v85h-288q-13 0 -22.5 9t-9.5 23v104q0 13 9.5 22.5t22.5 9.5h214l-321 578q-8 16 0 32q10 16 28 16h194q19 0 29 -18l215 -425q19 -38 56 -125q10 24 30.5 68t27.5 61 l191 420q8 19 29 19h191q17 0 27 -16q9 -14 1 -31l-313 -579h215q13 0 22.5 -9.5t9.5 -22.5v-104q0 -14 -9.5 -23t-22.5 -9h-290v-85h290q13 0 22.5 -9.5t9.5 -22.5v-103q0 -14 -9.5 -23t-22.5 -9h-290v-330q0 -13 -9.5 -22.5t-22.5 -9.5z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1043 971q0 100 -65 162t-171 62h-320v-448h320q106 0 171 62t65 162zM1280 971q0 -193 -126.5 -315t-326.5 -122h-340v-118h505q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-505v-192q0 -14 -9.5 -23t-22.5 -9h-167q-14 0 -23 9t-9 23v192h-224q-14 0 -23 9t-9 23v128 q0 14 9 23t23 9h224v118h-224q-14 0 -23 9t-9 23v149q0 13 9 22.5t23 9.5h224v629q0 14 9 23t23 9h539q200 0 326.5 -122t126.5 -315z" /> -<glyph unicode="" horiz-adv-x="1792" d="M514 341l81 299h-159l75 -300q1 -1 1 -3t1 -3q0 1 0.5 3.5t0.5 3.5zM630 768l35 128h-292l32 -128h225zM822 768h139l-35 128h-70zM1271 340l78 300h-162l81 -299q0 -1 0.5 -3.5t1.5 -3.5q0 1 0.5 3t0.5 3zM1382 768l33 128h-297l34 -128h230zM1792 736v-64q0 -14 -9 -23 t-23 -9h-213l-164 -616q-7 -24 -31 -24h-159q-24 0 -31 24l-166 616h-209l-167 -616q-7 -24 -31 -24h-159q-11 0 -19.5 7t-10.5 17l-160 616h-208q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h175l-33 128h-142q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h109l-89 344q-5 15 5 28 q10 12 26 12h137q26 0 31 -24l90 -360h359l97 360q7 24 31 24h126q24 0 31 -24l98 -360h365l93 360q5 24 31 24h137q16 0 26 -12q10 -13 5 -28l-91 -344h111q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-145l-34 -128h179q14 0 23 -9t9 -23z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1167 896q18 -182 -131 -258q117 -28 175 -103t45 -214q-7 -71 -32.5 -125t-64.5 -89t-97 -58.5t-121.5 -34.5t-145.5 -15v-255h-154v251q-80 0 -122 1v-252h-154v255q-18 0 -54 0.5t-55 0.5h-200l31 183h111q50 0 58 51v402h16q-6 1 -16 1v287q-13 68 -89 68h-111v164 l212 -1q64 0 97 1v252h154v-247q82 2 122 2v245h154v-252q79 -7 140 -22.5t113 -45t82.5 -78t36.5 -114.5zM952 351q0 36 -15 64t-37 46t-57.5 30.5t-65.5 18.5t-74 9t-69 3t-64.5 -1t-47.5 -1v-338q8 0 37 -0.5t48 -0.5t53 1.5t58.5 4t57 8.5t55.5 14t47.5 21t39.5 30 t24.5 40t9.5 51zM881 827q0 33 -12.5 58.5t-30.5 42t-48 28t-55 16.5t-61.5 8t-58 2.5t-54 -1t-39.5 -0.5v-307q5 0 34.5 -0.5t46.5 0t50 2t55 5.5t51.5 11t48.5 18.5t37 27t27 38.5t9 51z" /> -<glyph unicode="" d="M1024 1024v472q22 -14 36 -28l408 -408q14 -14 28 -36h-472zM896 992q0 -40 28 -68t68 -28h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h800v-544z" /> -<glyph unicode="" d="M1468 1060q14 -14 28 -36h-472v472q22 -14 36 -28zM992 896h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h800v-544q0 -40 28 -68t68 -28zM1152 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704 q14 0 23 9t9 23zM1152 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1152 672v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1191 1128h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1572 -23 v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -11v-2l14 2q9 2 30 2h248v119h121zM1661 874v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162 l230 -662h70z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1191 104h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1661 -150 v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162l230 -662h70zM1572 1001v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -10v-3l14 3q9 1 30 1h248 v119h121z" /> -<glyph unicode="" horiz-adv-x="1792" d="M736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1792 -32v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832 q14 0 23 -9t9 -23zM1600 480v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1408 992v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1216 1504v-192q0 -14 -9 -23t-23 -9h-256 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1216 -32v-192q0 -14 -9 -23t-23 -9h-256q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192 q14 0 23 -9t9 -23zM1408 480v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1600 992v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1792 1504v-192q0 -14 -9 -23t-23 -9h-832 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832q14 0 23 -9t9 -23z" /> -<glyph unicode="" d="M1346 223q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23 zM1486 165q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5 t82 -252.5zM1456 882v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165z" /> -<glyph unicode="" d="M1346 1247q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9 t9 -23zM1456 -142v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165zM1486 1189q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13 q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5t82 -252.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M256 192q0 26 -19 45t-45 19q-27 0 -45.5 -19t-18.5 -45q0 -27 18.5 -45.5t45.5 -18.5q26 0 45 18.5t19 45.5zM416 704v-640q0 -26 -19 -45t-45 -19h-288q-26 0 -45 19t-19 45v640q0 26 19 45t45 19h288q26 0 45 -19t19 -45zM1600 704q0 -86 -55 -149q15 -44 15 -76 q3 -76 -43 -137q17 -56 0 -117q-15 -57 -54 -94q9 -112 -49 -181q-64 -76 -197 -78h-36h-76h-17q-66 0 -144 15.5t-121.5 29t-120.5 39.5q-123 43 -158 44q-26 1 -45 19.5t-19 44.5v641q0 25 18 43.5t43 20.5q24 2 76 59t101 121q68 87 101 120q18 18 31 48t17.5 48.5 t13.5 60.5q7 39 12.5 61t19.5 52t34 50q19 19 45 19q46 0 82.5 -10.5t60 -26t40 -40.5t24 -45t12 -50t5 -45t0.5 -39q0 -38 -9.5 -76t-19 -60t-27.5 -56q-3 -6 -10 -18t-11 -22t-8 -24h277q78 0 135 -57t57 -135z" /> -<glyph unicode="" horiz-adv-x="1664" d="M256 960q0 -26 -19 -45t-45 -19q-27 0 -45.5 19t-18.5 45q0 27 18.5 45.5t45.5 18.5q26 0 45 -18.5t19 -45.5zM416 448v640q0 26 -19 45t-45 19h-288q-26 0 -45 -19t-19 -45v-640q0 -26 19 -45t45 -19h288q26 0 45 19t19 45zM1545 597q55 -61 55 -149q-1 -78 -57.5 -135 t-134.5 -57h-277q4 -14 8 -24t11 -22t10 -18q18 -37 27 -57t19 -58.5t10 -76.5q0 -24 -0.5 -39t-5 -45t-12 -50t-24 -45t-40 -40.5t-60 -26t-82.5 -10.5q-26 0 -45 19q-20 20 -34 50t-19.5 52t-12.5 61q-9 42 -13.5 60.5t-17.5 48.5t-31 48q-33 33 -101 120q-49 64 -101 121 t-76 59q-25 2 -43 20.5t-18 43.5v641q0 26 19 44.5t45 19.5q35 1 158 44q77 26 120.5 39.5t121.5 29t144 15.5h17h76h36q133 -2 197 -78q58 -69 49 -181q39 -37 54 -94q17 -61 0 -117q46 -61 43 -137q0 -32 -15 -76z" /> -<glyph unicode="" d="M919 233v157q0 50 -29 50q-17 0 -33 -16v-224q16 -16 33 -16q29 0 29 49zM1103 355h66v34q0 51 -33 51t-33 -51v-34zM532 621v-70h-80v-423h-74v423h-78v70h232zM733 495v-367h-67v40q-39 -45 -76 -45q-33 0 -42 28q-6 16 -6 54v290h66v-270q0 -24 1 -26q1 -15 15 -15 q20 0 42 31v280h67zM985 384v-146q0 -52 -7 -73q-12 -42 -53 -42q-35 0 -68 41v-36h-67v493h67v-161q32 40 68 40q41 0 53 -42q7 -21 7 -74zM1236 255v-9q0 -29 -2 -43q-3 -22 -15 -40q-27 -40 -80 -40q-52 0 -81 38q-21 27 -21 86v129q0 59 20 86q29 38 80 38t78 -38 q21 -28 21 -86v-76h-133v-65q0 -51 34 -51q24 0 30 26q0 1 0.5 7t0.5 16.5v21.5h68zM785 1079v-156q0 -51 -32 -51t-32 51v156q0 52 32 52t32 -52zM1318 366q0 177 -19 260q-10 44 -43 73.5t-76 34.5q-136 15 -412 15q-275 0 -411 -15q-44 -5 -76.5 -34.5t-42.5 -73.5 q-20 -87 -20 -260q0 -176 20 -260q10 -43 42.5 -73t75.5 -35q137 -15 412 -15t412 15q43 5 75.5 35t42.5 73q20 84 20 260zM563 1017l90 296h-75l-51 -195l-53 195h-78l24 -69t23 -69q35 -103 46 -158v-201h74v201zM852 936v130q0 58 -21 87q-29 38 -78 38q-51 0 -78 -38 q-21 -29 -21 -87v-130q0 -58 21 -87q27 -38 78 -38q49 0 78 38q21 27 21 87zM1033 816h67v370h-67v-283q-22 -31 -42 -31q-15 0 -16 16q-1 2 -1 26v272h-67v-293q0 -37 6 -55q11 -27 43 -27q36 0 77 45v-40zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M971 292v-211q0 -67 -39 -67q-23 0 -45 22v301q22 22 45 22q39 0 39 -67zM1309 291v-46h-90v46q0 68 45 68t45 -68zM343 509h107v94h-312v-94h105v-569h100v569zM631 -60h89v494h-89v-378q-30 -42 -57 -42q-18 0 -21 21q-1 3 -1 35v364h-89v-391q0 -49 8 -73 q12 -37 58 -37q48 0 102 61v-54zM1060 88v197q0 73 -9 99q-17 56 -71 56q-50 0 -93 -54v217h-89v-663h89v48q45 -55 93 -55q54 0 71 55q9 27 9 100zM1398 98v13h-91q0 -51 -2 -61q-7 -36 -40 -36q-46 0 -46 69v87h179v103q0 79 -27 116q-39 51 -106 51q-68 0 -107 -51 q-28 -37 -28 -116v-173q0 -79 29 -116q39 -51 108 -51q72 0 108 53q18 27 21 54q2 9 2 58zM790 1011v210q0 69 -43 69t-43 -69v-210q0 -70 43 -70t43 70zM1509 260q0 -234 -26 -350q-14 -59 -58 -99t-102 -46q-184 -21 -555 -21t-555 21q-58 6 -102.5 46t-57.5 99 q-26 112 -26 350q0 234 26 350q14 59 58 99t103 47q183 20 554 20t555 -20q58 -7 102.5 -47t57.5 -99q26 -112 26 -350zM511 1536h102l-121 -399v-271h-100v271q-14 74 -61 212q-37 103 -65 187h106l71 -263zM881 1203v-175q0 -81 -28 -118q-37 -51 -106 -51q-67 0 -105 51 q-28 38 -28 118v175q0 80 28 117q38 51 105 51q69 0 106 -51q28 -37 28 -117zM1216 1365v-499h-91v55q-53 -62 -103 -62q-46 0 -59 37q-8 24 -8 75v394h91v-367q0 -33 1 -35q3 -22 21 -22q27 0 57 43v381h91z" /> -<glyph unicode="" horiz-adv-x="1408" d="M597 869q-10 -18 -257 -456q-27 -46 -65 -46h-239q-21 0 -31 17t0 36l253 448q1 0 0 1l-161 279q-12 22 -1 37q9 15 32 15h239q40 0 66 -45zM1403 1511q11 -16 0 -37l-528 -934v-1l336 -615q11 -20 1 -37q-10 -15 -32 -15h-239q-42 0 -66 45l-339 622q18 32 531 942 q25 45 64 45h241q22 0 31 -15z" /> -<glyph unicode="" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" /> -<glyph unicode="" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" /> -<glyph unicode="" horiz-adv-x="1408" d="M928 135v-151l-707 -1v151zM1169 481v-701l-1 -35v-1h-1132l-35 1h-1v736h121v-618h928v618h120zM241 393l704 -65l-13 -150l-705 65zM309 709l683 -183l-39 -146l-683 183zM472 1058l609 -360l-77 -130l-609 360zM832 1389l398 -585l-124 -85l-399 584zM1285 1536 l121 -697l-149 -26l-121 697z" /> -<glyph unicode="" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" /> -<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" /> -<glyph unicode="" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M815 677q8 -63 -50.5 -101t-111.5 -6q-39 17 -53.5 58t-0.5 82t52 58q36 18 72.5 12t64 -35.5t27.5 -67.5zM926 698q-14 107 -113 164t-197 13q-63 -28 -100.5 -88.5t-34.5 -129.5q4 -91 77.5 -155t165.5 -56q91 8 152 84t50 168zM1165 1240q-20 27 -56 44.5t-58 22 t-71 12.5q-291 47 -566 -2q-43 -7 -66 -12t-55 -22t-50 -43q30 -28 76 -45.5t73.5 -22t87.5 -11.5q228 -29 448 -1q63 8 89.5 12t72.5 21.5t75 46.5zM1222 205q-8 -26 -15.5 -76.5t-14 -84t-28.5 -70t-58 -56.5q-86 -48 -189.5 -71.5t-202 -22t-201.5 18.5q-46 8 -81.5 18 t-76.5 27t-73 43.5t-52 61.5q-25 96 -57 292l6 16l18 9q223 -148 506.5 -148t507.5 148q21 -6 24 -23t-5 -45t-8 -37zM1403 1166q-26 -167 -111 -655q-5 -30 -27 -56t-43.5 -40t-54.5 -31q-252 -126 -610 -88q-248 27 -394 139q-15 12 -25.5 26.5t-17 35t-9 34t-6 39.5 t-5.5 35q-9 50 -26.5 150t-28 161.5t-23.5 147.5t-22 158q3 26 17.5 48.5t31.5 37.5t45 30t46 22.5t48 18.5q125 46 313 64q379 37 676 -50q155 -46 215 -122q16 -20 16.5 -51t-5.5 -54z" /> -<glyph unicode="" d="M848 666q0 43 -41 66t-77 1q-43 -20 -42.5 -72.5t43.5 -70.5q39 -23 81 4t36 72zM928 682q8 -66 -36 -121t-110 -61t-119 40t-56 113q-2 49 25.5 93t72.5 64q70 31 141.5 -10t81.5 -118zM1100 1073q-20 -21 -53.5 -34t-53 -16t-63.5 -8q-155 -20 -324 0q-44 6 -63 9.5 t-52.5 16t-54.5 32.5q13 19 36 31t40 15.5t47 8.5q198 35 408 1q33 -5 51 -8.5t43 -16t39 -31.5zM1142 327q0 7 5.5 26.5t3 32t-17.5 16.5q-161 -106 -365 -106t-366 106l-12 -6l-5 -12q26 -154 41 -210q47 -81 204 -108q249 -46 428 53q34 19 49 51.5t22.5 85.5t12.5 71z M1272 1020q9 53 -8 75q-43 55 -155 88q-216 63 -487 36q-132 -12 -226 -46q-38 -15 -59.5 -25t-47 -34t-29.5 -54q8 -68 19 -138t29 -171t24 -137q1 -5 5 -31t7 -36t12 -27t22 -28q105 -80 284 -100q259 -28 440 63q24 13 39.5 23t31 29t19.5 40q48 267 80 473zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1024" d="M944 207l80 -237q-23 -35 -111 -66t-177 -32q-104 -2 -190.5 26t-142.5 74t-95 106t-55.5 120t-16.5 118v544h-168v215q72 26 129 69.5t91 90t58 102t34 99t15 88.5q1 5 4.5 8.5t7.5 3.5h244v-424h333v-252h-334v-518q0 -30 6.5 -56t22.5 -52.5t49.5 -41.5t81.5 -14 q78 2 134 29z" /> -<glyph unicode="" d="M1136 75l-62 183q-44 -22 -103 -22q-36 -1 -62 10.5t-38.5 31.5t-17.5 40.5t-5 43.5v398h257v194h-256v326h-188q-8 0 -9 -10q-5 -44 -17.5 -87t-39 -95t-77 -95t-118.5 -68v-165h130v-418q0 -57 21.5 -115t65 -111t121 -85.5t176.5 -30.5q69 1 136.5 25t85.5 50z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="768" d="M765 237q8 -19 -5 -35l-350 -384q-10 -10 -23 -10q-14 0 -24 10l-355 384q-13 16 -5 35q9 19 29 19h224v1248q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1248h224q21 0 29 -19z" /> -<glyph unicode="" horiz-adv-x="768" d="M765 1043q-9 -19 -29 -19h-224v-1248q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1248h-224q-21 0 -29 19t5 35l350 384q10 10 23 10q14 0 24 -10l355 -384q13 -16 5 -35z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 736v-192q0 -14 -9 -23t-23 -9h-1248v-224q0 -21 -19 -29t-35 5l-384 350q-10 10 -10 23q0 14 10 24l384 354q16 14 35 6q19 -9 19 -29v-224h1248q14 0 23 -9t9 -23z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1728 643q0 -14 -10 -24l-384 -354q-16 -14 -35 -6q-19 9 -19 29v224h-1248q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h1248v224q0 21 19 29t35 -5l384 -350q10 -10 10 -23z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1393 321q-39 -125 -123 -250q-129 -196 -257 -196q-49 0 -140 32q-86 32 -151 32q-61 0 -142 -33q-81 -34 -132 -34q-152 0 -301 259q-147 261 -147 503q0 228 113 374q112 144 284 144q72 0 177 -30q104 -30 138 -30q45 0 143 34q102 34 173 34q119 0 213 -65 q52 -36 104 -100q-79 -67 -114 -118q-65 -94 -65 -207q0 -124 69 -223t158 -126zM1017 1494q0 -61 -29 -136q-30 -75 -93 -138q-54 -54 -108 -72q-37 -11 -104 -17q3 149 78 257q74 107 250 148q1 -3 2.5 -11t2.5 -11q0 -4 0.5 -10t0.5 -10z" /> -<glyph unicode="" horiz-adv-x="1664" d="M682 530v-651l-682 94v557h682zM682 1273v-659h-682v565zM1664 530v-786l-907 125v661h907zM1664 1408v-794h-907v669z" /> -<glyph unicode="" horiz-adv-x="1408" d="M493 1053q16 0 27.5 11.5t11.5 27.5t-11.5 27.5t-27.5 11.5t-27 -11.5t-11 -27.5t11 -27.5t27 -11.5zM915 1053q16 0 27 11.5t11 27.5t-11 27.5t-27 11.5t-27.5 -11.5t-11.5 -27.5t11.5 -27.5t27.5 -11.5zM103 869q42 0 72 -30t30 -72v-430q0 -43 -29.5 -73t-72.5 -30 t-73 30t-30 73v430q0 42 30 72t73 30zM1163 850v-666q0 -46 -32 -78t-77 -32h-75v-227q0 -43 -30 -73t-73 -30t-73 30t-30 73v227h-138v-227q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73l-1 227h-74q-46 0 -78 32t-32 78v666h918zM931 1255q107 -55 171 -153.5t64 -215.5 h-925q0 117 64 215.5t172 153.5l-71 131q-7 13 5 20q13 6 20 -6l72 -132q95 42 201 42t201 -42l72 132q7 12 20 6q12 -7 5 -20zM1408 767v-430q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73v430q0 43 30 72.5t72 29.5q43 0 73 -29.5t30 -72.5z" /> -<glyph unicode="" d="M663 1125q-11 -1 -15.5 -10.5t-8.5 -9.5q-5 -1 -5 5q0 12 19 15h10zM750 1111q-4 -1 -11.5 6.5t-17.5 4.5q24 11 32 -2q3 -6 -3 -9zM399 684q-4 1 -6 -3t-4.5 -12.5t-5.5 -13.5t-10 -13q-7 -10 -1 -12q4 -1 12.5 7t12.5 18q1 3 2 7t2 6t1.5 4.5t0.5 4v3t-1 2.5t-3 2z M1254 325q0 18 -55 42q4 15 7.5 27.5t5 26t3 21.5t0.5 22.5t-1 19.5t-3.5 22t-4 20.5t-5 25t-5.5 26.5q-10 48 -47 103t-72 75q24 -20 57 -83q87 -162 54 -278q-11 -40 -50 -42q-31 -4 -38.5 18.5t-8 83.5t-11.5 107q-9 39 -19.5 69t-19.5 45.5t-15.5 24.5t-13 15t-7.5 7 q-14 62 -31 103t-29.5 56t-23.5 33t-15 40q-4 21 6 53.5t4.5 49.5t-44.5 25q-15 3 -44.5 18t-35.5 16q-8 1 -11 26t8 51t36 27q37 3 51 -30t4 -58q-11 -19 -2 -26.5t30 -0.5q13 4 13 36v37q-5 30 -13.5 50t-21 30.5t-23.5 15t-27 7.5q-107 -8 -89 -134q0 -15 -1 -15 q-9 9 -29.5 10.5t-33 -0.5t-15.5 5q1 57 -16 90t-45 34q-27 1 -41.5 -27.5t-16.5 -59.5q-1 -15 3.5 -37t13 -37.5t15.5 -13.5q10 3 16 14q4 9 -7 8q-7 0 -15.5 14.5t-9.5 33.5q-1 22 9 37t34 14q17 0 27 -21t9.5 -39t-1.5 -22q-22 -15 -31 -29q-8 -12 -27.5 -23.5 t-20.5 -12.5q-13 -14 -15.5 -27t7.5 -18q14 -8 25 -19.5t16 -19t18.5 -13t35.5 -6.5q47 -2 102 15q2 1 23 7t34.5 10.5t29.5 13t21 17.5q9 14 20 8q5 -3 6.5 -8.5t-3 -12t-16.5 -9.5q-20 -6 -56.5 -21.5t-45.5 -19.5q-44 -19 -70 -23q-25 -5 -79 2q-10 2 -9 -2t17 -19 q25 -23 67 -22q17 1 36 7t36 14t33.5 17.5t30 17t24.5 12t17.5 2.5t8.5 -11q0 -2 -1 -4.5t-4 -5t-6 -4.5t-8.5 -5t-9 -4.5t-10 -5t-9.5 -4.5q-28 -14 -67.5 -44t-66.5 -43t-49 -1q-21 11 -63 73q-22 31 -25 22q-1 -3 -1 -10q0 -25 -15 -56.5t-29.5 -55.5t-21 -58t11.5 -63 q-23 -6 -62.5 -90t-47.5 -141q-2 -18 -1.5 -69t-5.5 -59q-8 -24 -29 -3q-32 31 -36 94q-2 28 4 56q4 19 -1 18l-4 -5q-36 -65 10 -166q5 -12 25 -28t24 -20q20 -23 104 -90.5t93 -76.5q16 -15 17.5 -38t-14 -43t-45.5 -23q8 -15 29 -44.5t28 -54t7 -70.5q46 24 7 92 q-4 8 -10.5 16t-9.5 12t-2 6q3 5 13 9.5t20 -2.5q46 -52 166 -36q133 15 177 87q23 38 34 30q12 -6 10 -52q-1 -25 -23 -92q-9 -23 -6 -37.5t24 -15.5q3 19 14.5 77t13.5 90q2 21 -6.5 73.5t-7.5 97t23 70.5q15 18 51 18q1 37 34.5 53t72.5 10.5t60 -22.5zM626 1152 q3 17 -2.5 30t-11.5 15q-9 2 -9 -7q2 -5 5 -6q10 0 7 -15q-3 -20 8 -20q3 0 3 3zM1045 955q-2 8 -6.5 11.5t-13 5t-14.5 5.5q-5 3 -9.5 8t-7 8t-5.5 6.5t-4 4t-4 -1.5q-14 -16 7 -43.5t39 -31.5q9 -1 14.5 8t3.5 20zM867 1168q0 11 -5 19.5t-11 12.5t-9 3q-14 -1 -7 -7l4 -2 q14 -4 18 -31q0 -3 8 2zM921 1401q0 2 -2.5 5t-9 7t-9.5 6q-15 15 -24 15q-9 -1 -11.5 -7.5t-1 -13t-0.5 -12.5q-1 -4 -6 -10.5t-6 -9t3 -8.5q4 -3 8 0t11 9t15 9q1 1 9 1t15 2t9 7zM1486 60q20 -12 31 -24.5t12 -24t-2.5 -22.5t-15.5 -22t-23.5 -19.5t-30 -18.5 t-31.5 -16.5t-32 -15.5t-27 -13q-38 -19 -85.5 -56t-75.5 -64q-17 -16 -68 -19.5t-89 14.5q-18 9 -29.5 23.5t-16.5 25.5t-22 19.5t-47 9.5q-44 1 -130 1q-19 0 -57 -1.5t-58 -2.5q-44 -1 -79.5 -15t-53.5 -30t-43.5 -28.5t-53.5 -11.5q-29 1 -111 31t-146 43q-19 4 -51 9.5 t-50 9t-39.5 9.5t-33.5 14.5t-17 19.5q-10 23 7 66.5t18 54.5q1 16 -4 40t-10 42.5t-4.5 36.5t10.5 27q14 12 57 14t60 12q30 18 42 35t12 51q21 -73 -32 -106q-32 -20 -83 -15q-34 3 -43 -10q-13 -15 5 -57q2 -6 8 -18t8.5 -18t4.5 -17t1 -22q0 -15 -17 -49t-14 -48 q3 -17 37 -26q20 -6 84.5 -18.5t99.5 -20.5q24 -6 74 -22t82.5 -23t55.5 -4q43 6 64.5 28t23 48t-7.5 58.5t-19 52t-20 36.5q-121 190 -169 242q-68 74 -113 40q-11 -9 -15 15q-3 16 -2 38q1 29 10 52t24 47t22 42q8 21 26.5 72t29.5 78t30 61t39 54q110 143 124 195 q-12 112 -16 310q-2 90 24 151.5t106 104.5q39 21 104 21q53 1 106 -13.5t89 -41.5q57 -42 91.5 -121.5t29.5 -147.5q-5 -95 30 -214q34 -113 133 -218q55 -59 99.5 -163t59.5 -191q8 -49 5 -84.5t-12 -55.5t-20 -22q-10 -2 -23.5 -19t-27 -35.5t-40.5 -33.5t-61 -14 q-18 1 -31.5 5t-22.5 13.5t-13.5 15.5t-11.5 20.5t-9 19.5q-22 37 -41 30t-28 -49t7 -97q20 -70 1 -195q-10 -65 18 -100.5t73 -33t85 35.5q59 49 89.5 66.5t103.5 42.5q53 18 77 36.5t18.5 34.5t-25 28.5t-51.5 23.5q-33 11 -49.5 48t-15 72.5t15.5 47.5q1 -31 8 -56.5 t14.5 -40.5t20.5 -28.5t21 -19t21.5 -13t16.5 -9.5z" /> -<glyph unicode="" d="M1024 36q-42 241 -140 498h-2l-2 -1q-16 -6 -43 -16.5t-101 -49t-137 -82t-131 -114.5t-103 -148l-15 11q184 -150 418 -150q132 0 256 52zM839 643q-21 49 -53 111q-311 -93 -673 -93q-1 -7 -1 -21q0 -124 44 -236.5t124 -201.5q50 89 123.5 166.5t142.5 124.5t130.5 81 t99.5 48l37 13q4 1 13 3.5t13 4.5zM732 855q-120 213 -244 378q-138 -65 -234 -186t-128 -272q302 0 606 80zM1416 536q-210 60 -409 29q87 -239 128 -469q111 75 185 189.5t96 250.5zM611 1277q-1 0 -2 -1q1 1 2 1zM1201 1132q-185 164 -433 164q-76 0 -155 -19 q131 -170 246 -382q69 26 130 60.5t96.5 61.5t65.5 57t37.5 40.5zM1424 647q-3 232 -149 410l-1 -1q-9 -12 -19 -24.5t-43.5 -44.5t-71 -60.5t-100 -65t-131.5 -64.5q25 -53 44 -95q2 -6 6.5 -17.5t7.5 -16.5q36 5 74.5 7t73.5 2t69 -1.5t64 -4t56.5 -5.5t48 -6.5t36.5 -6 t25 -4.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1173 473q0 50 -19.5 91.5t-48.5 68.5t-73 49t-82.5 34t-87.5 23l-104 24q-30 7 -44 10.5t-35 11.5t-30 16t-16.5 21t-7.5 30q0 77 144 77q43 0 77 -12t54 -28.5t38 -33.5t40 -29t48 -12q47 0 75.5 32t28.5 77q0 55 -56 99.5t-142 67.5t-182 23q-68 0 -132 -15.5 t-119.5 -47t-89 -87t-33.5 -128.5q0 -61 19 -106.5t56 -75.5t80 -48.5t103 -32.5l146 -36q90 -22 112 -36q32 -20 32 -60q0 -39 -40 -64.5t-105 -25.5q-51 0 -91.5 16t-65 38.5t-45.5 45t-46 38.5t-54 16q-50 0 -75.5 -30t-25.5 -75q0 -92 122 -157.5t291 -65.5 q73 0 140 18.5t122.5 53.5t88.5 93.5t33 131.5zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5q-130 0 -234 80q-77 -16 -150 -16q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5q0 73 16 150q-80 104 -80 234q0 159 112.5 271.5t271.5 112.5q130 0 234 -80 q77 16 150 16q143 0 273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -73 -16 -150q80 -104 80 -234z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1000 1102l37 194q5 23 -9 40t-35 17h-712q-23 0 -38.5 -17t-15.5 -37v-1101q0 -7 6 -1l291 352q23 26 38 33.5t48 7.5h239q22 0 37 14.5t18 29.5q24 130 37 191q4 21 -11.5 40t-36.5 19h-294q-29 0 -48 19t-19 48v42q0 29 19 47.5t48 18.5h346q18 0 35 13.5t20 29.5z M1227 1324q-15 -73 -53.5 -266.5t-69.5 -350t-35 -173.5q-6 -22 -9 -32.5t-14 -32.5t-24.5 -33t-38.5 -21t-58 -10h-271q-13 0 -22 -10q-8 -9 -426 -494q-22 -25 -58.5 -28.5t-48.5 5.5q-55 22 -55 98v1410q0 55 38 102.5t120 47.5h888q95 0 127 -53t10 -159zM1227 1324 l-158 -790q4 17 35 173.5t69.5 350t53.5 266.5z" /> -<glyph unicode="" d="M704 192v1024q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-1024q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1376 576v640q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-640q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408 q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1280 480q0 -40 -28 -68t-68 -28q-51 0 -80 43l-227 341h-45v-132l247 -411q9 -15 9 -33q0 -26 -19 -45t-45 -19h-192v-272q0 -46 -33 -79t-79 -33h-160q-46 0 -79 33t-33 79v272h-192q-26 0 -45 19t-19 45q0 18 9 33l247 411v132h-45l-227 -341q-29 -43 -80 -43 q-40 0 -68 28t-28 68q0 29 16 53l256 384q73 107 176 107h384q103 0 176 -107l256 -384q16 -24 16 -53zM864 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" /> -<glyph unicode="" horiz-adv-x="1024" d="M1024 832v-416q0 -40 -28 -68t-68 -28t-68 28t-28 68v352h-64v-912q0 -46 -33 -79t-79 -33t-79 33t-33 79v464h-64v-464q0 -46 -33 -79t-79 -33t-79 33t-33 79v912h-64v-352q0 -40 -28 -68t-68 -28t-68 28t-28 68v416q0 80 56 136t136 56h640q80 0 136 -56t56 -136z M736 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" /> -<glyph unicode="" d="M773 234l350 473q16 22 24.5 59t-6 85t-61.5 79q-40 26 -83 25.5t-73.5 -17.5t-54.5 -45q-36 -40 -96 -40q-59 0 -95 40q-24 28 -54.5 45t-73.5 17.5t-84 -25.5q-46 -31 -60.5 -79t-6 -85t24.5 -59zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1472 640q0 117 -45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5zM1748 363q-4 -15 -20 -20l-292 -96v-306q0 -16 -13 -26q-15 -10 -29 -4 l-292 94l-180 -248q-10 -13 -26 -13t-26 13l-180 248l-292 -94q-14 -6 -29 4q-13 10 -13 26v306l-292 96q-16 5 -20 20q-5 17 4 29l180 248l-180 248q-9 13 -4 29q4 15 20 20l292 96v306q0 16 13 26q15 10 29 4l292 -94l180 248q9 12 26 12t26 -12l180 -248l292 94 q14 6 29 -4q13 -10 13 -26v-306l292 -96q16 -5 20 -20q5 -16 -4 -29l-180 -248l180 -248q9 -12 4 -29z" /> -<glyph unicode="" d="M1262 233q-54 -9 -110 -9q-182 0 -337 90t-245 245t-90 337q0 192 104 357q-201 -60 -328.5 -229t-127.5 -384q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51q144 0 273.5 61.5t220.5 171.5zM1465 318q-94 -203 -283.5 -324.5t-413.5 -121.5q-156 0 -298 61 t-245 164t-164 245t-61 298q0 153 57.5 292.5t156 241.5t235.5 164.5t290 68.5q44 2 61 -39q18 -41 -15 -72q-86 -78 -131.5 -181.5t-45.5 -218.5q0 -148 73 -273t198 -198t273 -73q118 0 228 51q41 18 72 -13q14 -14 17.5 -34t-4.5 -38z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1088 704q0 26 -19 45t-45 19h-256q-26 0 -45 -19t-19 -45t19 -45t45 -19h256q26 0 45 19t19 45zM1664 896v-960q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v960q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1728 1344v-256q0 -26 -19 -45t-45 -19h-1536 q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1536q26 0 45 -19t19 -45z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1632 576q0 -26 -19 -45t-45 -19h-224q0 -171 -67 -290l208 -209q19 -19 19 -45t-19 -45q-18 -19 -45 -19t-45 19l-198 197q-5 -5 -15 -13t-42 -28.5t-65 -36.5t-82 -29t-97 -13v896h-128v-896q-51 0 -101.5 13.5t-87 33t-66 39t-43.5 32.5l-15 14l-183 -207 q-20 -21 -48 -21q-24 0 -43 16q-19 18 -20.5 44.5t15.5 46.5l202 227q-58 114 -58 274h-224q-26 0 -45 19t-19 45t19 45t45 19h224v294l-173 173q-19 19 -19 45t19 45t45 19t45 -19l173 -173h844l173 173q19 19 45 19t45 -19t19 -45t-19 -45l-173 -173v-294h224q26 0 45 -19 t19 -45zM1152 1152h-640q0 133 93.5 226.5t226.5 93.5t226.5 -93.5t93.5 -226.5z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1917 1016q23 -64 -150 -294q-24 -32 -65 -85q-78 -100 -90 -131q-17 -41 14 -81q17 -21 81 -82h1l1 -1l1 -1l2 -2q141 -131 191 -221q3 -5 6.5 -12.5t7 -26.5t-0.5 -34t-25 -27.5t-59 -12.5l-256 -4q-24 -5 -56 5t-52 22l-20 12q-30 21 -70 64t-68.5 77.5t-61 58 t-56.5 15.5q-3 -1 -8 -3.5t-17 -14.5t-21.5 -29.5t-17 -52t-6.5 -77.5q0 -15 -3.5 -27.5t-7.5 -18.5l-4 -5q-18 -19 -53 -22h-115q-71 -4 -146 16.5t-131.5 53t-103 66t-70.5 57.5l-25 24q-10 10 -27.5 30t-71.5 91t-106 151t-122.5 211t-130.5 272q-6 16 -6 27t3 16l4 6 q15 19 57 19l274 2q12 -2 23 -6.5t16 -8.5l5 -3q16 -11 24 -32q20 -50 46 -103.5t41 -81.5l16 -29q29 -60 56 -104t48.5 -68.5t41.5 -38.5t34 -14t27 5q2 1 5 5t12 22t13.5 47t9.5 81t0 125q-2 40 -9 73t-14 46l-6 12q-25 34 -85 43q-13 2 5 24q17 19 38 30q53 26 239 24 q82 -1 135 -13q20 -5 33.5 -13.5t20.5 -24t10.5 -32t3.5 -45.5t-1 -55t-2.5 -70.5t-1.5 -82.5q0 -11 -1 -42t-0.5 -48t3.5 -40.5t11.5 -39t22.5 -24.5q8 -2 17 -4t26 11t38 34.5t52 67t68 107.5q60 104 107 225q4 10 10 17.5t11 10.5l4 3l5 2.5t13 3t20 0.5l288 2 q39 5 64 -2.5t31 -16.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M675 252q21 34 11 69t-45 50q-34 14 -73 1t-60 -46q-22 -34 -13 -68.5t43 -50.5t74.5 -2.5t62.5 47.5zM769 373q8 13 3.5 26.5t-17.5 18.5q-14 5 -28.5 -0.5t-21.5 -18.5q-17 -31 13 -45q14 -5 29 0.5t22 18.5zM943 266q-45 -102 -158 -150t-224 -12 q-107 34 -147.5 126.5t6.5 187.5q47 93 151.5 139t210.5 19q111 -29 158.5 -119.5t2.5 -190.5zM1255 426q-9 96 -89 170t-208.5 109t-274.5 21q-223 -23 -369.5 -141.5t-132.5 -264.5q9 -96 89 -170t208.5 -109t274.5 -21q223 23 369.5 141.5t132.5 264.5zM1563 422 q0 -68 -37 -139.5t-109 -137t-168.5 -117.5t-226 -83t-270.5 -31t-275 33.5t-240.5 93t-171.5 151t-65 199.5q0 115 69.5 245t197.5 258q169 169 341.5 236t246.5 -7q65 -64 20 -209q-4 -14 -1 -20t10 -7t14.5 0.5t13.5 3.5l6 2q139 59 246 59t153 -61q45 -63 0 -178 q-2 -13 -4.5 -20t4.5 -12.5t12 -7.5t17 -6q57 -18 103 -47t80 -81.5t34 -116.5zM1489 1046q42 -47 54.5 -108.5t-6.5 -117.5q-8 -23 -29.5 -34t-44.5 -4q-23 8 -34 29.5t-4 44.5q20 63 -24 111t-107 35q-24 -5 -45 8t-25 37q-5 24 8 44.5t37 25.5q60 13 119 -5.5t101 -65.5z M1670 1209q87 -96 112.5 -222.5t-13.5 -241.5q-9 -27 -34 -40t-52 -4t-40 34t-5 52q28 82 10 172t-80 158q-62 69 -148 95.5t-173 8.5q-28 -6 -52 9.5t-30 43.5t9.5 51.5t43.5 29.5q123 26 244 -11.5t208 -134.5z" /> -<glyph unicode="" d="M1133 -34q-171 -94 -368 -94q-196 0 -367 94q138 87 235.5 211t131.5 268q35 -144 132.5 -268t235.5 -211zM638 1394v-485q0 -252 -126.5 -459.5t-330.5 -306.5q-181 215 -181 495q0 187 83.5 349.5t229.5 269.5t325 137zM1536 638q0 -280 -181 -495 q-204 99 -330.5 306.5t-126.5 459.5v485q179 -30 325 -137t229.5 -269.5t83.5 -349.5z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1402 433q-32 -80 -76 -138t-91 -88.5t-99 -46.5t-101.5 -14.5t-96.5 8.5t-86.5 22t-69.5 27.5t-46 22.5l-17 10q-113 -228 -289.5 -359.5t-384.5 -132.5q-19 0 -32 13t-13 32t13 31.5t32 12.5q173 1 322.5 107.5t251.5 294.5q-36 -14 -72 -23t-83 -13t-91 2.5t-93 28.5 t-92 59t-84.5 100t-74.5 146q114 47 214 57t167.5 -7.5t124.5 -56.5t88.5 -77t56.5 -82q53 131 79 291q-7 -1 -18 -2.5t-46.5 -2.5t-69.5 0.5t-81.5 10t-88.5 23t-84 42.5t-75 65t-54.5 94.5t-28.5 127.5q70 28 133.5 36.5t112.5 -1t92 -30t73.5 -50t56 -61t42 -63t27.5 -56 t16 -39.5l4 -16q12 122 12 195q-8 6 -21.5 16t-49 44.5t-63.5 71.5t-54 93t-33 112.5t12 127t70 138.5q73 -25 127.5 -61.5t84.5 -76.5t48 -85t20.5 -89t-0.5 -85.5t-13 -76.5t-19 -62t-17 -42l-7 -15q1 -5 1 -50.5t-1 -71.5q3 7 10 18.5t30.5 43t50.5 58t71 55.5t91.5 44.5 t112 14.5t132.5 -24q-2 -78 -21.5 -141.5t-50 -104.5t-69.5 -71.5t-81.5 -45.5t-84.5 -24t-80 -9.5t-67.5 1t-46.5 4.5l-17 3q-23 -147 -73 -283q6 7 18 18.5t49.5 41t77.5 52.5t99.5 42t117.5 20t129 -23.5t137 -77.5z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1259 283v-66q0 -85 -57.5 -144.5t-138.5 -59.5h-57l-260 -269v269h-529q-81 0 -138.5 59.5t-57.5 144.5v66h1238zM1259 609v-255h-1238v255h1238zM1259 937v-255h-1238v255h1238zM1259 1077v-67h-1238v67q0 84 57.5 143.5t138.5 59.5h846q81 0 138.5 -59.5t57.5 -143.5z " /> -<glyph unicode="" d="M1152 640q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1152 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-192q0 -14 -9 -23t-23 -9q-12 0 -24 10l-319 319q-9 9 -9 23t9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h352q13 0 22.5 -9.5t9.5 -22.5zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1024 960v-640q0 -26 -19 -45t-45 -19q-20 0 -37 12l-448 320q-27 19 -27 52t27 52l448 320q17 12 37 12q26 0 45 -19t19 -45zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5 t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1023 349l102 -204q-58 -179 -210 -290t-339 -111q-156 0 -288.5 77.5t-210 210t-77.5 288.5q0 181 104.5 330t274.5 211l17 -131q-122 -54 -195 -165.5t-73 -244.5q0 -185 131.5 -316.5t316.5 -131.5q126 0 232.5 65t165 175.5t49.5 236.5zM1571 249l58 -114l-256 -128 q-13 -7 -29 -7q-40 0 -57 35l-239 477h-472q-24 0 -42.5 16.5t-21.5 40.5l-96 779q-2 16 6 42q14 51 57 82.5t97 31.5q66 0 113 -47t47 -113q0 -69 -52 -117.5t-120 -41.5l37 -289h423v-128h-407l16 -128h455q40 0 57 -35l228 -455z" /> -<glyph unicode="" d="M1254 899q16 85 -21 132q-52 65 -187 45q-17 -3 -41 -12.5t-57.5 -30.5t-64.5 -48.5t-59.5 -70t-44.5 -91.5q80 7 113.5 -16t26.5 -99q-5 -52 -52 -143q-43 -78 -71 -99q-44 -32 -87 14q-23 24 -37.5 64.5t-19 73t-10 84t-8.5 71.5q-23 129 -34 164q-12 37 -35.5 69 t-50.5 40q-57 16 -127 -25q-54 -32 -136.5 -106t-122.5 -102v-7q16 -8 25.5 -26t21.5 -20q21 -3 54.5 8.5t58 10.5t41.5 -30q11 -18 18.5 -38.5t15 -48t12.5 -40.5q17 -46 53 -187q36 -146 57 -197q42 -99 103 -125q43 -12 85 -1.5t76 31.5q131 77 250 237 q104 139 172.5 292.5t82.5 226.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1152" d="M1152 704q0 -191 -94.5 -353t-256.5 -256.5t-353 -94.5h-160q-14 0 -23 9t-9 23v611l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v93l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v250q0 14 9 23t23 9h160 q14 0 23 -9t9 -23v-181l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-93l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-487q188 13 318 151t130 328q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" /> -<glyph unicode="" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-352v-352q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v352h-352q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h352v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-352h352q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832 q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="2176" d="M620 416q-110 -64 -268 -64h-128v64h-64q-13 0 -22.5 23.5t-9.5 56.5q0 24 7 49q-58 2 -96.5 10.5t-38.5 20.5t38.5 20.5t96.5 10.5q-7 25 -7 49q0 33 9.5 56.5t22.5 23.5h64v64h128q158 0 268 -64h1113q42 -7 106.5 -18t80.5 -14q89 -15 150 -40.5t83.5 -47.5t22.5 -40 t-22.5 -40t-83.5 -47.5t-150 -40.5q-16 -3 -80.5 -14t-106.5 -18h-1113zM1739 668q53 -36 53 -92t-53 -92l81 -30q68 48 68 122t-68 122zM625 400h1015q-217 -38 -456 -80q-57 0 -113 -24t-83 -48l-28 -24l-288 -288q-26 -26 -70.5 -45t-89.5 -19h-96l-93 464h29 q157 0 273 64zM352 816h-29l93 464h96q46 0 90 -19t70 -45l288 -288q4 -4 11 -10.5t30.5 -23t48.5 -29t61.5 -23t72.5 -10.5l456 -80h-1015q-116 64 -273 64z" /> -<glyph unicode="" horiz-adv-x="1664" d="M1519 760q62 0 103.5 -40.5t41.5 -101.5q0 -97 -93 -130l-172 -59l56 -167q7 -21 7 -47q0 -59 -42 -102t-101 -43q-47 0 -85.5 27t-53.5 72l-55 165l-310 -106l55 -164q8 -24 8 -47q0 -59 -42 -102t-102 -43q-47 0 -85 27t-53 72l-55 163l-153 -53q-29 -9 -50 -9 q-61 0 -101.5 40t-40.5 101q0 47 27.5 85t71.5 53l156 53l-105 313l-156 -54q-26 -8 -48 -8q-60 0 -101 40.5t-41 100.5q0 47 27.5 85t71.5 53l157 53l-53 159q-8 24 -8 47q0 60 42 102.5t102 42.5q47 0 85 -27t53 -72l54 -160l310 105l-54 160q-8 24 -8 47q0 59 42.5 102 t101.5 43q47 0 85.5 -27.5t53.5 -71.5l53 -161l162 55q21 6 43 6q60 0 102.5 -39.5t42.5 -98.5q0 -45 -30 -81.5t-74 -51.5l-157 -54l105 -316l164 56q24 8 46 8zM725 498l310 105l-105 315l-310 -107z" /> -<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM1280 352v436q-31 -35 -64 -55q-34 -22 -132.5 -85t-151.5 -99q-98 -69 -164 -69v0v0q-66 0 -164 69 q-46 32 -141.5 92.5t-142.5 92.5q-12 8 -33 27t-31 27v-436q0 -40 28 -68t68 -28h832q40 0 68 28t28 68zM1280 925q0 41 -27.5 70t-68.5 29h-832q-40 0 -68 -28t-28 -68q0 -37 30.5 -76.5t67.5 -64.5q47 -32 137.5 -89t129.5 -83q3 -2 17 -11.5t21 -14t21 -13t23.5 -13 t21.5 -9.5t22.5 -7.5t20.5 -2.5t20.5 2.5t22.5 7.5t21.5 9.5t23.5 13t21 13t21 14t17 11.5l267 174q35 23 66.5 62.5t31.5 73.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M127 640q0 163 67 313l367 -1005q-196 95 -315 281t-119 411zM1415 679q0 -19 -2.5 -38.5t-10 -49.5t-11.5 -44t-17.5 -59t-17.5 -58l-76 -256l-278 826q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-75 1 -202 10q-12 1 -20.5 -5t-11.5 -15t-1.5 -18.5t9 -16.5 t19.5 -8l80 -8l120 -328l-168 -504l-280 832q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-7 0 -23 0.5t-26 0.5q105 160 274.5 253.5t367.5 93.5q147 0 280.5 -53t238.5 -149h-10q-55 0 -92 -40.5t-37 -95.5q0 -12 2 -24t4 -21.5t8 -23t9 -21t12 -22.5t12.5 -21 t14.5 -24t14 -23q63 -107 63 -212zM909 573l237 -647q1 -6 5 -11q-126 -44 -255 -44q-112 0 -217 32zM1570 1009q95 -174 95 -369q0 -209 -104 -385.5t-279 -278.5l235 678q59 169 59 276q0 42 -6 79zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286 t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 -215q173 0 331.5 68t273 182.5t182.5 273t68 331.5t-68 331.5t-182.5 273t-273 182.5t-331.5 68t-331.5 -68t-273 -182.5t-182.5 -273t-68 -331.5t68 -331.5t182.5 -273 t273 -182.5t331.5 -68z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1086 1536v-1536l-272 -128q-228 20 -414 102t-293 208.5t-107 272.5q0 140 100.5 263.5t275 205.5t391.5 108v-172q-217 -38 -356.5 -150t-139.5 -255q0 -152 154.5 -267t388.5 -145v1360zM1755 954l37 -390l-525 114l147 83q-119 70 -280 99v172q277 -33 481 -157z" /> -<glyph unicode="" horiz-adv-x="2048" d="M960 1536l960 -384v-128h-128q0 -26 -20.5 -45t-48.5 -19h-1526q-28 0 -48.5 19t-20.5 45h-128v128zM256 896h256v-768h128v768h256v-768h128v768h256v-768h128v768h256v-768h59q28 0 48.5 -19t20.5 -45v-64h-1664v64q0 26 20.5 45t48.5 19h59v768zM1851 -64 q28 0 48.5 -19t20.5 -45v-128h-1920v128q0 26 20.5 45t48.5 19h1782z" /> -<glyph unicode="" horiz-adv-x="2304" d="M1774 700l18 -316q4 -69 -82 -128t-235 -93.5t-323 -34.5t-323 34.5t-235 93.5t-82 128l18 316l574 -181q22 -7 48 -7t48 7zM2304 1024q0 -23 -22 -31l-1120 -352q-4 -1 -10 -1t-10 1l-652 206q-43 -34 -71 -111.5t-34 -178.5q63 -36 63 -109q0 -69 -58 -107l58 -433 q2 -14 -8 -25q-9 -11 -24 -11h-192q-15 0 -24 11q-10 11 -8 25l58 433q-58 38 -58 107q0 73 65 111q11 207 98 330l-333 104q-22 8 -22 31t22 31l1120 352q4 1 10 1t10 -1l1120 -352q22 -8 22 -31z" /> -<glyph unicode="" d="M859 579l13 -707q-62 11 -105 11q-41 0 -105 -11l13 707q-40 69 -168.5 295.5t-216.5 374.5t-181 287q58 -15 108 -15q43 0 111 15q63 -111 133.5 -229.5t167 -276.5t138.5 -227q37 61 109.5 177.5t117.5 190t105 176t107 189.5q54 -14 107 -14q56 0 114 14v0 q-28 -39 -60 -88.5t-49.5 -78.5t-56.5 -96t-49 -84q-146 -248 -353 -610z" /> -<glyph unicode="" horiz-adv-x="1280" d="M981 197q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -49 2q-53 0 -104.5 -7t-107 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -56 23.5 -102t61 -75.5t87 -50t100 -29t101.5 -8.5q58 0 111.5 13t99 39t73 73t27.5 109zM864 1055 q0 59 -17 125.5t-48 129t-84 103.5t-117 41q-42 0 -82.5 -19.5t-66.5 -52.5q-46 -59 -46 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26q37 0 77.5 16.5t65.5 43.5q53 56 53 159zM752 1536h417l-137 -88h-132q75 -63 113 -133t38 -160q0 -72 -24.5 -129.5 t-59.5 -93t-69.5 -65t-59 -61.5t-24.5 -66q0 -36 32 -70.5t77 -68t90.5 -73.5t77.5 -104t32 -142q0 -91 -49 -173q-71 -122 -209.5 -179.5t-298.5 -57.5q-132 0 -246.5 41.5t-172.5 137.5q-36 59 -36 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 41 -47.5 73.5 t-15.5 73.5q0 40 21 85q-46 -4 -68 -4q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q76 66 182 98t218 32z" /> -<glyph unicode="" horiz-adv-x="1984" d="M831 572q0 -56 -40.5 -96t-96.5 -40q-57 0 -98 40t-41 96q0 57 41.5 98t97.5 41t96.5 -41t40.5 -98zM1292 711q56 0 96.5 -41t40.5 -98q0 -56 -40.5 -96t-96.5 -40q-57 0 -98 40t-41 96q0 57 41.5 98t97.5 41zM1984 722q0 -62 -31 -114t-83 -82q5 -33 5 -61 q0 -121 -68.5 -230.5t-197.5 -193.5q-125 -82 -285.5 -125.5t-335.5 -43.5q-176 0 -336.5 43.5t-284.5 125.5q-129 84 -197.5 193t-68.5 231q0 29 5 66q-48 31 -77 81.5t-29 109.5q0 94 66 160t160 66q83 0 148 -55q248 158 592 164l134 423q4 14 17.5 21.5t28.5 4.5 l347 -82q22 50 68.5 81t102.5 31q77 0 131.5 -54.5t54.5 -131.5t-54.5 -132t-131.5 -55q-76 0 -130.5 54t-55.5 131l-315 74l-116 -366q327 -14 560 -166q64 58 151 58q94 0 160 -66t66 -160zM1664 1459q-45 0 -77 -32t-32 -77t32 -77t77 -32t77 32t32 77t-32 77t-77 32z M77 722q0 -67 51 -111q49 131 180 235q-36 25 -82 25q-62 0 -105.5 -43.5t-43.5 -105.5zM1567 105q112 73 171.5 166t59.5 194t-59.5 193.5t-171.5 165.5q-116 75 -265.5 115.5t-313.5 40.5t-313.5 -40.5t-265.5 -115.5q-112 -73 -171.5 -165.5t-59.5 -193.5t59.5 -194 t171.5 -166q116 -75 265.5 -115.5t313.5 -40.5t313.5 40.5t265.5 115.5zM1850 605q57 46 57 117q0 62 -43.5 105.5t-105.5 43.5q-49 0 -86 -28q131 -105 178 -238zM1258 237q11 11 27 11t27 -11t11 -27.5t-11 -27.5q-99 -99 -319 -99h-2q-220 0 -319 99q-11 11 -11 27.5 t11 27.5t27 11t27 -11q77 -77 265 -77h2q188 0 265 77z" /> -<glyph unicode="" d="M950 393q7 7 17.5 7t17.5 -7t7 -18t-7 -18q-65 -64 -208 -64h-1h-1q-143 0 -207 64q-8 7 -8 18t8 18q7 7 17.5 7t17.5 -7q49 -51 172 -51h1h1q122 0 173 51zM671 613q0 -37 -26 -64t-63 -27t-63 27t-26 64t26 63t63 26t63 -26t26 -63zM1214 1049q-29 0 -50 21t-21 50 q0 30 21 51t50 21q30 0 51 -21t21 -51q0 -29 -21 -50t-51 -21zM1216 1408q132 0 226 -94t94 -227v-894q0 -133 -94 -227t-226 -94h-896q-132 0 -226 94t-94 227v894q0 133 94 227t226 94h896zM1321 596q35 14 57 45.5t22 70.5q0 51 -36 87.5t-87 36.5q-60 0 -98 -48 q-151 107 -375 115l83 265l206 -49q1 -50 36.5 -85t84.5 -35q50 0 86 35.5t36 85.5t-36 86t-86 36q-36 0 -66 -20.5t-45 -53.5l-227 54q-9 2 -17.5 -2.5t-11.5 -14.5l-95 -302q-224 -4 -381 -113q-36 43 -93 43q-51 0 -87 -36.5t-36 -87.5q0 -37 19.5 -67.5t52.5 -45.5 q-7 -25 -7 -54q0 -98 74 -181.5t201.5 -132t278.5 -48.5q150 0 277.5 48.5t201.5 132t74 181.5q0 27 -6 54zM971 702q37 0 63 -26t26 -63t-26 -64t-63 -27t-63 27t-26 64t26 63t63 26z" /> -<glyph unicode="" d="M866 697l90 27v62q0 79 -58 135t-138 56t-138 -55.5t-58 -134.5v-283q0 -20 -14 -33.5t-33 -13.5t-32.5 13.5t-13.5 33.5v120h-151v-122q0 -82 57.5 -139t139.5 -57q81 0 138.5 56.5t57.5 136.5v280q0 19 13.5 33t33.5 14q19 0 32.5 -14t13.5 -33v-54zM1199 502v122h-150 v-126q0 -20 -13.5 -33.5t-33.5 -13.5q-19 0 -32.5 14t-13.5 33v123l-90 -26l-60 28v-123q0 -80 58 -137t139 -57t138.5 57t57.5 139zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103 t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1920" d="M1062 824v118q0 42 -30 72t-72 30t-72 -30t-30 -72v-612q0 -175 -126 -299t-303 -124q-178 0 -303.5 125.5t-125.5 303.5v266h328v-262q0 -43 30 -72.5t72 -29.5t72 29.5t30 72.5v620q0 171 126.5 292t301.5 121q176 0 302 -122t126 -294v-136l-195 -58zM1592 602h328 v-266q0 -178 -125.5 -303.5t-303.5 -125.5q-177 0 -303 124.5t-126 300.5v268l131 -61l195 58v-270q0 -42 30 -71.5t72 -29.5t72 29.5t30 71.5v275z" /> -<glyph unicode="" d="M1472 160v480h-704v704h-480q-93 0 -158.5 -65.5t-65.5 -158.5v-480h704v-704h480q93 0 158.5 65.5t65.5 158.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="2048" d="M328 1254h204v-983h-532v697h328v286zM328 435v369h-123v-369h123zM614 968v-697h205v697h-205zM614 1254v-204h205v204h-205zM901 968h533v-942h-533v163h328v82h-328v697zM1229 435v369h-123v-369h123zM1516 968h532v-942h-532v163h327v82h-327v697zM1843 435v369h-123 v-369h123z" /> -<glyph unicode="" d="M1046 516q0 -64 -38 -109t-91 -45q-43 0 -70 15v277q28 17 70 17q53 0 91 -45.5t38 -109.5zM703 944q0 -64 -38 -109.5t-91 -45.5q-43 0 -70 15v277q28 17 70 17q53 0 91 -45t38 -109zM1265 513q0 134 -88 229t-213 95q-20 0 -39 -3q-23 -78 -78 -136q-87 -95 -211 -101 v-636l211 41v206q51 -19 117 -19q125 0 213 95t88 229zM922 940q0 134 -88.5 229t-213.5 95q-74 0 -141 -36h-186v-840l211 41v206q55 -19 116 -19q125 0 213.5 95t88.5 229zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="2038" d="M1222 607q75 3 143.5 -20.5t118 -58.5t101 -94.5t84 -108t75.5 -120.5q33 -56 78.5 -109t75.5 -80.5t99 -88.5q-48 -30 -108.5 -57.5t-138.5 -59t-114 -47.5q-44 37 -74 115t-43.5 164.5t-33 180.5t-42.5 168.5t-72.5 123t-122.5 48.5l-10 -2l-6 -4q4 -5 13 -14 q6 -5 28 -23.5t25.5 -22t19 -18t18 -20.5t11.5 -21t10.5 -27.5t4.5 -31t4 -40.5l1 -33q1 -26 -2.5 -57.5t-7.5 -52t-12.5 -58.5t-11.5 -53q-35 1 -101 -9.5t-98 -10.5q-39 0 -72 10q-2 16 -2 47q0 74 3 96q2 13 31.5 41.5t57 59t26.5 51.5q-24 2 -43 -24 q-36 -53 -111.5 -99.5t-136.5 -46.5q-25 0 -75.5 63t-106.5 139.5t-84 96.5q-6 4 -27 30q-482 -112 -513 -112q-16 0 -28 11t-12 27q0 15 8.5 26.5t22.5 14.5l486 106q-8 14 -8 25t5.5 17.5t16 11.5t20 7t23 4.5t18.5 4.5q4 1 15.5 7.5t17.5 6.5q15 0 28 -16t20 -33 q163 37 172 37q17 0 29.5 -11t12.5 -28q0 -15 -8.5 -26t-23.5 -14l-182 -40l-1 -16q-1 -26 81.5 -117.5t104.5 -91.5q47 0 119 80t72 129q0 36 -23.5 53t-51 18.5t-51 11.5t-23.5 34q0 16 10 34l-68 19q43 44 43 117q0 26 -5 58q82 16 144 16q44 0 71.5 -1.5t48.5 -8.5 t31 -13.5t20.5 -24.5t15.5 -33.5t17 -47.5t24 -60l50 25q-3 -40 -23 -60t-42.5 -21t-40 -6.5t-16.5 -20.5zM1282 842q-5 5 -13.5 15.5t-12 14.5t-10.5 11.5t-10 10.5l-8 8t-8.5 7.5t-8 5t-8.5 4.5q-7 3 -14.5 5t-20.5 2.5t-22 0.5h-32.5h-37.5q-126 0 -217 -43 q16 30 36 46.5t54 29.5t65.5 36t46 36.5t50 55t43.5 50.5q12 -9 28 -31.5t32 -36.5t38 -13l12 1v-76l22 -1q247 95 371 190q28 21 50 39t42.5 37.5t33 31t29.5 34t24 31t24.5 37t23 38t27 47.5t29.5 53l7 9q-2 -53 -43 -139q-79 -165 -205 -264t-306 -142q-14 -3 -42 -7.5 t-50 -9.5t-39 -14q3 -19 24.5 -46t21.5 -34q0 -11 -26 -30zM1061 -79q39 26 131.5 47.5t146.5 21.5q9 0 22.5 -15.5t28 -42.5t26 -50t24 -51t14.5 -33q-121 -45 -244 -45q-61 0 -125 11zM822 568l48 12l109 -177l-73 -48zM1323 51q3 -15 3 -16q0 -7 -17.5 -14.5t-46 -13 t-54 -9.5t-53.5 -7.5t-32 -4.5l-7 43q21 2 60.5 8.5t72 10t60.5 3.5h14zM866 679l-96 -20l-6 17q10 1 32.5 7t34.5 6q19 0 35 -10zM1061 45h31l10 -83l-41 -12v95zM1950 1535v1v-1zM1950 1535l-1 -5l-2 -2l1 3zM1950 1535l1 1z" /> -<glyph unicode="" d="M1167 -50q-5 19 -24 5q-30 -22 -87 -39t-131 -17q-129 0 -193 49q-5 4 -13 4q-11 0 -26 -12q-7 -6 -7.5 -16t7.5 -20q34 -32 87.5 -46t102.5 -12.5t99 4.5q41 4 84.5 20.5t65 30t28.5 20.5q12 12 7 29zM1128 65q-19 47 -39 61q-23 15 -76 15q-47 0 -71 -10 q-29 -12 -78 -56q-26 -24 -12 -44q9 -8 17.5 -4.5t31.5 23.5q3 2 10.5 8.5t10.5 8.5t10 7t11.5 7t12.5 5t15 4.5t16.5 2.5t20.5 1q27 0 44.5 -7.5t23 -14.5t13.5 -22q10 -17 12.5 -20t12.5 1q23 12 14 34zM1483 346q0 22 -5 44.5t-16.5 45t-34 36.5t-52.5 14 q-33 0 -97 -41.5t-129 -83.5t-101 -42q-27 -1 -63.5 19t-76 49t-83.5 58t-100 49t-111 19q-115 -1 -197 -78.5t-84 -178.5q-2 -112 74 -164q29 -20 62.5 -28.5t103.5 -8.5q57 0 132 32.5t134 71t120 70.5t93 31q26 -1 65 -31.5t71.5 -67t68 -67.5t55.5 -32q35 -3 58.5 14 t55.5 63q28 41 42.5 101t14.5 106zM1536 506q0 -164 -62 -304.5t-166 -236t-242.5 -149.5t-290.5 -54t-293 57.5t-247.5 157t-170.5 241.5t-64 302q0 89 19.5 172.5t49 145.5t70.5 118.5t78.5 94t78.5 69.5t64.5 46.5t42.5 24.5q14 8 51 26.5t54.5 28.5t48 30t60.5 44 q36 28 58 72.5t30 125.5q129 -155 186 -193q44 -29 130 -68t129 -66q21 -13 39 -25t60.5 -46.5t76 -70.5t75 -95t69 -122t47 -148.5t19.5 -177.5z" /> -<glyph unicode="" d="M1070 463l-160 -160l-151 -152l-30 -30q-65 -64 -151.5 -87t-171.5 -2q-16 -70 -72 -115t-129 -45q-85 0 -145 60.5t-60 145.5q0 72 44.5 128t113.5 72q-22 86 1 173t88 152l12 12l151 -152l-11 -11q-37 -37 -37 -89t37 -90q37 -37 89 -37t89 37l30 30l151 152l161 160z M729 1145l12 -12l-152 -152l-12 12q-37 37 -89 37t-89 -37t-37 -89.5t37 -89.5l29 -29l152 -152l160 -160l-151 -152l-161 160l-151 152l-30 30q-68 67 -90 159.5t5 179.5q-70 15 -115 71t-45 129q0 85 60 145.5t145 60.5q76 0 133.5 -49t69.5 -123q84 20 169.5 -3.5 t149.5 -87.5zM1536 78q0 -85 -60 -145.5t-145 -60.5q-74 0 -131 47t-71 118q-86 -28 -179.5 -6t-161.5 90l-11 12l151 152l12 -12q37 -37 89 -37t89 37t37 89t-37 89l-30 30l-152 152l-160 160l152 152l160 -160l152 -152l29 -30q64 -64 87.5 -150.5t2.5 -171.5 q76 -11 126.5 -68.5t50.5 -134.5zM1534 1202q0 -77 -51 -135t-127 -69q26 -85 3 -176.5t-90 -158.5l-12 -12l-151 152l12 12q37 37 37 89t-37 89t-89 37t-89 -37l-30 -30l-152 -152l-160 -160l-152 152l161 160l152 152l29 30q67 67 159 89.5t178 -3.5q11 75 68.5 126 t135.5 51q85 0 145 -60.5t60 -145.5z" /> -<glyph unicode="" d="M654 458q-1 -3 -12.5 0.5t-31.5 11.5l-20 9q-44 20 -87 49q-7 5 -41 31.5t-38 28.5q-67 -103 -134 -181q-81 -95 -105 -110q-4 -2 -19.5 -4t-18.5 0q6 4 82 92q21 24 85.5 115t78.5 118q17 30 51 98.5t36 77.5q-8 1 -110 -33q-8 -2 -27.5 -7.5t-34.5 -9.5t-17 -5 q-2 -2 -2 -10.5t-1 -9.5q-5 -10 -31 -15q-23 -7 -47 0q-18 4 -28 21q-4 6 -5 23q6 2 24.5 5t29.5 6q58 16 105 32q100 35 102 35q10 2 43 19.5t44 21.5q9 3 21.5 8t14.5 5.5t6 -0.5q2 -12 -1 -33q0 -2 -12.5 -27t-26.5 -53.5t-17 -33.5q-25 -50 -77 -131l64 -28 q12 -6 74.5 -32t67.5 -28q4 -1 10.5 -25.5t4.5 -30.5zM449 944q3 -15 -4 -28q-12 -23 -50 -38q-30 -12 -60 -12q-26 3 -49 26q-14 15 -18 41l1 3q3 -3 19.5 -5t26.5 0t58 16q36 12 55 14q17 0 21 -17zM1147 815l63 -227l-139 42zM39 15l694 232v1032l-694 -233v-1031z M1280 332l102 -31l-181 657l-100 31l-216 -536l102 -31l45 110l211 -65zM777 1294l573 -184v380zM1088 -29l158 -13l-54 -160l-40 66q-130 -83 -276 -108q-58 -12 -91 -12h-84q-79 0 -199.5 39t-183.5 85q-8 7 -8 16q0 8 5 13.5t13 5.5q4 0 18 -7.5t30.5 -16.5t20.5 -11 q73 -37 159.5 -61.5t157.5 -24.5q95 0 167 14.5t157 50.5q15 7 30.5 15.5t34 19t28.5 16.5zM1536 1050v-1079l-774 246q-14 -6 -375 -127.5t-368 -121.5q-13 0 -18 13q0 1 -1 3v1078q3 9 4 10q5 6 20 11q106 35 149 50v384l558 -198q2 0 160.5 55t316 108.5t161.5 53.5 q20 0 20 -21v-418z" /> -<glyph unicode="" horiz-adv-x="1792" d="M288 1152q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-128q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h128zM1664 989q58 -34 93 -93t35 -128v-768q0 -106 -75 -181t-181 -75h-864q-66 0 -113 47t-47 113v1536q0 40 28 68t68 28h672q40 0 88 -20t76 -48 l152 -152q28 -28 48 -76t20 -88v-163zM928 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM928 256v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM928 512v128q0 14 -9 23 t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1184 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1184 256v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128 q14 0 23 9t9 23zM1184 512v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 256v128q0 14 -9 23t-23 9h-128 q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 512v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1536 896v256h-160q-40 0 -68 28t-28 68v160h-640v-512h896z" /> -<glyph unicode="" d="M1344 1536q26 0 45 -19t19 -45v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280zM512 1248v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 992v-64q0 -14 9 -23t23 -9h64q14 0 23 9 t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 736v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 480v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM384 160v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64 q14 0 23 9t9 23zM384 928v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 -96v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9 t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM896 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 928v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 160v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64 q14 0 23 9t9 23zM1152 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 928v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9 t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1188 988l-292 -292v-824q0 -46 -33 -79t-79 -33t-79 33t-33 79v384h-64v-384q0 -46 -33 -79t-79 -33t-79 33t-33 79v824l-292 292q-28 28 -28 68t28 68t68 28t68 -28l228 -228h368l228 228q28 28 68 28t68 -28t28 -68t-28 -68zM864 1152q0 -93 -65.5 -158.5 t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M780 1064q0 -60 -19 -113.5t-63 -92.5t-105 -39q-76 0 -138 57.5t-92 135.5t-30 151q0 60 19 113.5t63 92.5t105 39q77 0 138.5 -57.5t91.5 -135t30 -151.5zM438 581q0 -80 -42 -139t-119 -59q-76 0 -141.5 55.5t-100.5 133.5t-35 152q0 80 42 139.5t119 59.5 q76 0 141.5 -55.5t100.5 -134t35 -152.5zM832 608q118 0 255 -97.5t229 -237t92 -254.5q0 -46 -17 -76.5t-48.5 -45t-64.5 -20t-76 -5.5q-68 0 -187.5 45t-182.5 45q-66 0 -192.5 -44.5t-200.5 -44.5q-183 0 -183 146q0 86 56 191.5t139.5 192.5t187.5 146t193 59zM1071 819 q-61 0 -105 39t-63 92.5t-19 113.5q0 74 30 151.5t91.5 135t138.5 57.5q61 0 105 -39t63 -92.5t19 -113.5q0 -73 -30 -151t-92 -135.5t-138 -57.5zM1503 923q77 0 119 -59.5t42 -139.5q0 -74 -35 -152t-100.5 -133.5t-141.5 -55.5q-77 0 -119 59t-42 139q0 74 35 152.5 t100.5 134t141.5 55.5z" /> -<glyph unicode="" horiz-adv-x="768" d="M704 1008q0 -145 -57 -243.5t-152 -135.5l45 -821q2 -26 -16 -45t-44 -19h-192q-26 0 -44 19t-16 45l45 821q-95 37 -152 135.5t-57 243.5q0 128 42.5 249.5t117.5 200t160 78.5t160 -78.5t117.5 -200t42.5 -249.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M896 -93l640 349v636l-640 -233v-752zM832 772l698 254l-698 254l-698 -254zM1664 1024v-768q0 -35 -18 -65t-49 -47l-704 -384q-28 -16 -61 -16t-61 16l-704 384q-31 17 -49 47t-18 65v768q0 40 23 73t61 47l704 256q22 8 44 8t44 -8l704 -256q38 -14 61 -47t23 -73z " /> -<glyph unicode="" horiz-adv-x="2304" d="M640 -96l384 192v314l-384 -164v-342zM576 358l404 173l-404 173l-404 -173zM1664 -96l384 192v314l-384 -164v-342zM1600 358l404 173l-404 173l-404 -173zM1152 651l384 165v266l-384 -164v-267zM1088 1030l441 189l-441 189l-441 -189zM2176 512v-416q0 -36 -19 -67 t-52 -47l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-5 2 -7 4q-2 -2 -7 -4l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-33 16 -52 47t-19 67v416q0 38 21.5 70t56.5 48l434 186v400q0 38 21.5 70t56.5 48l448 192q23 10 50 10t50 -10l448 -192q35 -16 56.5 -48t21.5 -70 v-400l434 -186q36 -16 57 -48t21 -70z" /> -<glyph unicode="" horiz-adv-x="2048" d="M1848 1197h-511v-124h511v124zM1596 771q-90 0 -146 -52.5t-62 -142.5h408q-18 195 -200 195zM1612 186q63 0 122 32t76 87h221q-100 -307 -427 -307q-214 0 -340.5 132t-126.5 347q0 208 130.5 345.5t336.5 137.5q138 0 240.5 -68t153 -179t50.5 -248q0 -17 -2 -47h-658 q0 -111 57.5 -171.5t166.5 -60.5zM277 236h296q205 0 205 167q0 180 -199 180h-302v-347zM277 773h281q78 0 123.5 36.5t45.5 113.5q0 144 -190 144h-260v-294zM0 1282h594q87 0 155 -14t126.5 -47.5t90 -96.5t31.5 -154q0 -181 -172 -263q114 -32 172 -115t58 -204 q0 -75 -24.5 -136.5t-66 -103.5t-98.5 -71t-121 -42t-134 -13h-611v1260z" /> -<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM499 1041h-371v-787h382q117 0 197 57.5t80 170.5q0 158 -143 200q107 52 107 164q0 57 -19.5 96.5 t-56.5 60.5t-79 29.5t-97 8.5zM477 723h-176v184h163q119 0 119 -90q0 -94 -106 -94zM486 388h-185v217h189q124 0 124 -113q0 -104 -128 -104zM1136 356q-68 0 -104 38t-36 107h411q1 10 1 30q0 132 -74.5 220.5t-203.5 88.5q-128 0 -210 -86t-82 -216q0 -135 79 -217 t213 -82q205 0 267 191h-138q-11 -34 -47.5 -54t-75.5 -20zM1126 722q113 0 124 -122h-254q4 56 39 89t91 33zM964 988h319v-77h-319v77z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1582 954q0 -101 -71.5 -172.5t-172.5 -71.5t-172.5 71.5t-71.5 172.5t71.5 172.5t172.5 71.5t172.5 -71.5t71.5 -172.5zM812 212q0 104 -73 177t-177 73q-27 0 -54 -6l104 -42q77 -31 109.5 -106.5t1.5 -151.5q-31 -77 -107 -109t-152 -1q-21 8 -62 24.5t-61 24.5 q32 -60 91 -96.5t130 -36.5q104 0 177 73t73 177zM1642 953q0 126 -89.5 215.5t-215.5 89.5q-127 0 -216.5 -89.5t-89.5 -215.5q0 -127 89.5 -216t216.5 -89q126 0 215.5 89t89.5 216zM1792 953q0 -189 -133.5 -322t-321.5 -133l-437 -319q-12 -129 -109 -218t-229 -89 q-121 0 -214 76t-118 192l-230 92v429l389 -157q79 48 173 48q13 0 35 -2l284 407q2 187 135.5 319t320.5 132q188 0 321.5 -133.5t133.5 -321.5z" /> -<glyph unicode="" d="M1242 889q0 80 -57 136.5t-137 56.5t-136.5 -57t-56.5 -136q0 -80 56.5 -136.5t136.5 -56.5t137 56.5t57 136.5zM632 301q0 -83 -58 -140.5t-140 -57.5q-56 0 -103 29t-72 77q52 -20 98 -40q60 -24 120 1.5t85 86.5q24 60 -1.5 120t-86.5 84l-82 33q22 5 42 5 q82 0 140 -57.5t58 -140.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v153l172 -69q20 -92 93.5 -152t168.5 -60q104 0 181 70t87 173l345 252q150 0 255.5 105.5t105.5 254.5q0 150 -105.5 255.5t-255.5 105.5 q-148 0 -253 -104.5t-107 -252.5l-225 -322q-9 1 -28 1q-75 0 -137 -37l-297 119v468q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5zM1289 887q0 -100 -71 -170.5t-171 -70.5t-170.5 70.5t-70.5 170.5t70.5 171t170.5 71q101 0 171.5 -70.5t70.5 -171.5z " /> -<glyph unicode="" horiz-adv-x="1792" d="M836 367l-15 -368l-2 -22l-420 29q-36 3 -67 31.5t-47 65.5q-11 27 -14.5 55t4 65t12 55t21.5 64t19 53q78 -12 509 -28zM449 953l180 -379l-147 92q-63 -72 -111.5 -144.5t-72.5 -125t-39.5 -94.5t-18.5 -63l-4 -21l-190 357q-17 26 -18 56t6 47l8 18q35 63 114 188 l-140 86zM1680 436l-188 -359q-12 -29 -36.5 -46.5t-43.5 -20.5l-18 -4q-71 -7 -219 -12l8 -164l-230 367l211 362l7 -173q170 -16 283 -5t170 33zM895 1360q-47 -63 -265 -435l-317 187l-19 12l225 356q20 31 60 45t80 10q24 -2 48.5 -12t42 -21t41.5 -33t36 -34.5 t36 -39.5t32 -35zM1550 1053l212 -363q18 -37 12.5 -76t-27.5 -74q-13 -20 -33 -37t-38 -28t-48.5 -22t-47 -16t-51.5 -14t-46 -12q-34 72 -265 436l313 195zM1407 1279l142 83l-220 -373l-419 20l151 86q-34 89 -75 166t-75.5 123.5t-64.5 80t-47 46.5l-17 13l405 -1 q31 3 58 -10.5t39 -28.5l11 -15q39 -61 112 -190z" /> -<glyph unicode="" horiz-adv-x="2048" d="M480 448q0 66 -47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47t113 47t47 113zM516 768h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5zM1888 448q0 66 -47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47t113 47t47 113zM2048 544v-384 q0 -14 -9 -23t-23 -9h-96v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-1024v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5t179 63.5h768q98 0 179 -63.5t104 -157.5 l105 -419h28q93 0 158.5 -65.5t65.5 -158.5z" /> -<glyph unicode="" horiz-adv-x="2048" d="M1824 640q93 0 158.5 -65.5t65.5 -158.5v-384q0 -14 -9 -23t-23 -9h-96v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-1024v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5 t179 63.5h128v224q0 14 9 23t23 9h448q14 0 23 -9t9 -23v-224h128q98 0 179 -63.5t104 -157.5l105 -419h28zM320 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM516 640h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5z M1728 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47z" /> -<glyph unicode="" d="M1504 64q0 -26 -19 -45t-45 -19h-462q1 -17 6 -87.5t5 -108.5q0 -25 -18 -42.5t-43 -17.5h-320q-25 0 -43 17.5t-18 42.5q0 38 5 108.5t6 87.5h-462q-26 0 -45 19t-19 45t19 45l402 403h-229q-26 0 -45 19t-19 45t19 45l402 403h-197q-26 0 -45 19t-19 45t19 45l384 384 q19 19 45 19t45 -19l384 -384q19 -19 19 -45t-19 -45t-45 -19h-197l402 -403q19 -19 19 -45t-19 -45t-45 -19h-229l402 -403q19 -19 19 -45z" /> -<glyph unicode="" d="M1127 326q0 32 -30 51q-193 115 -447 115q-133 0 -287 -34q-42 -9 -42 -52q0 -20 13.5 -34.5t35.5 -14.5q5 0 37 8q132 27 243 27q226 0 397 -103q19 -11 33 -11q19 0 33 13.5t14 34.5zM1223 541q0 40 -35 61q-237 141 -548 141q-153 0 -303 -42q-48 -13 -48 -64 q0 -25 17.5 -42.5t42.5 -17.5q7 0 37 8q122 33 251 33q279 0 488 -124q24 -13 38 -13q25 0 42.5 17.5t17.5 42.5zM1331 789q0 47 -40 70q-126 73 -293 110.5t-343 37.5q-204 0 -364 -47q-23 -7 -38.5 -25.5t-15.5 -48.5q0 -31 20.5 -52t51.5 -21q11 0 40 8q133 37 307 37 q159 0 309.5 -34t253.5 -95q21 -12 40 -12q29 0 50.5 20.5t21.5 51.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1397 1408q58 0 98.5 -40.5t40.5 -98.5v-1258q0 -58 -40.5 -98.5t-98.5 -40.5h-1258q-58 0 -98.5 40.5t-40.5 98.5v1258q0 58 40.5 98.5t98.5 40.5h1258zM1465 11v1258q0 28 -20 48t-48 20h-1258q-28 0 -48 -20t-20 -48v-1258q0 -28 20 -48t48 -20h1258q28 0 48 20t20 48 zM694 749l188 -387l533 145v-496q0 -7 -5.5 -12.5t-12.5 -5.5h-1258q-7 0 -12.5 5.5t-5.5 12.5v141l711 195l-212 439q4 1 12 2.5t12 1.5q170 32 303.5 21.5t221 -46t143.5 -94.5q27 -28 -25 -42q-64 -16 -256 -62l-97 198q-111 7 -240 -16zM1397 1287q7 0 12.5 -5.5 t5.5 -12.5v-428q-85 30 -188 52q-294 64 -645 12l-18 -3l-65 134h-233l85 -190q-132 -51 -230 -137v560q0 7 5.5 12.5t12.5 5.5h1258zM286 387q-14 -3 -26 4.5t-14 21.5q-24 203 166 305l129 -270z" /> -<glyph unicode="" horiz-adv-x="2304" d="M784 164l16 241l-16 523q-1 10 -7.5 17t-16.5 7q-9 0 -16 -7t-7 -17l-14 -523l14 -241q1 -10 7.5 -16.5t15.5 -6.5q22 0 24 23zM1080 193l11 211l-12 586q0 16 -13 24q-8 5 -16 5t-16 -5q-13 -8 -13 -24l-1 -6l-10 -579q0 -1 11 -236v-1q0 -10 6 -17q9 -11 23 -11 q11 0 20 9q9 7 9 20zM35 533l20 -128l-20 -126q-2 -9 -9 -9t-9 9l-17 126l17 128q2 9 9 9t9 -9zM121 612l26 -207l-26 -203q-2 -9 -10 -9q-9 0 -9 10l-23 202l23 207q0 9 9 9q8 0 10 -9zM401 159zM213 650l25 -245l-25 -237q0 -11 -11 -11q-10 0 -12 11l-21 237l21 245 q2 12 12 12q11 0 11 -12zM307 657l23 -252l-23 -244q-2 -13 -14 -13q-13 0 -13 13l-21 244l21 252q0 13 13 13q12 0 14 -13zM401 639l21 -234l-21 -246q-2 -16 -16 -16q-6 0 -10.5 4.5t-4.5 11.5l-20 246l20 234q0 6 4.5 10.5t10.5 4.5q14 0 16 -15zM784 164zM495 785 l21 -380l-21 -246q0 -7 -5 -12.5t-12 -5.5q-16 0 -18 18l-18 246l18 380q2 18 18 18q7 0 12 -5.5t5 -12.5zM589 871l19 -468l-19 -244q0 -8 -5.5 -13.5t-13.5 -5.5q-18 0 -20 19l-16 244l16 468q2 19 20 19q8 0 13.5 -5.5t5.5 -13.5zM687 911l18 -506l-18 -242 q-2 -21 -22 -21q-19 0 -21 21l-16 242l16 506q0 9 6.5 15.5t14.5 6.5q9 0 15 -6.5t7 -15.5zM1079 169v0v0zM881 915l15 -510l-15 -239q0 -10 -7.5 -17.5t-17.5 -7.5t-17 7t-8 18l-14 239l14 510q0 11 7.5 18t17.5 7t17.5 -7t7.5 -18zM980 896l14 -492l-14 -236q0 -11 -8 -19 t-19 -8t-19 8t-9 19l-12 236l12 492q1 12 9 20t19 8t18.5 -8t8.5 -20zM1192 404l-14 -231v0q0 -13 -9 -22t-22 -9t-22 9t-10 22l-6 114l-6 117l12 636v3q2 15 12 24q9 7 20 7q8 0 15 -5q14 -8 16 -26zM2304 423q0 -117 -83 -199.5t-200 -82.5h-786q-13 2 -22 11t-9 22v899 q0 23 28 33q85 34 181 34q195 0 338 -131.5t160 -323.5q53 22 110 22q117 0 200 -83t83 -201z" /> -<glyph unicode="" d="M768 768q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 0q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127 t443 -43zM768 384q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 1536q208 0 385 -34.5t280 -93.5t103 -128v-128q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5 t-103 128v128q0 69 103 128t280 93.5t385 34.5z" /> -<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M894 465q33 -26 84 -56q59 7 117 7q147 0 177 -49q16 -22 2 -52q0 -1 -1 -2l-2 -2v-1q-6 -38 -71 -38q-48 0 -115 20t-130 53q-221 -24 -392 -83q-153 -262 -242 -262q-15 0 -28 7l-24 12q-1 1 -6 5q-10 10 -6 36q9 40 56 91.5t132 96.5q14 9 23 -6q2 -2 2 -4q52 85 107 197 q68 136 104 262q-24 82 -30.5 159.5t6.5 127.5q11 40 42 40h21h1q23 0 35 -15q18 -21 9 -68q-2 -6 -4 -8q1 -3 1 -8v-30q-2 -123 -14 -192q55 -164 146 -238zM318 54q52 24 137 158q-51 -40 -87.5 -84t-49.5 -74zM716 974q-15 -42 -2 -132q1 7 7 44q0 3 7 43q1 4 4 8 q-1 1 -1 2t-0.5 1.5t-0.5 1.5q-1 22 -13 36q0 -1 -1 -2v-2zM592 313q135 54 284 81q-2 1 -13 9.5t-16 13.5q-76 67 -127 176q-27 -86 -83 -197q-30 -56 -45 -83zM1238 329q-24 24 -140 24q76 -28 124 -28q14 0 18 1q0 1 -2 3z" /> -<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M233 768v-107h70l164 -661h159l128 485q7 20 10 46q2 16 2 24h4l3 -24q1 -3 3.5 -20t5.5 -26l128 -485h159l164 661h70v107h-300v-107h90l-99 -438q-5 -20 -7 -46l-2 -21h-4l-3 21q-1 5 -4 21t-5 25l-144 545h-114l-144 -545q-2 -9 -4.5 -24.5t-3.5 -21.5l-4 -21h-4l-2 21 q-2 26 -7 46l-99 438h90v107h-300z" /> -<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M429 106v-106h281v106h-75l103 161q5 7 10 16.5t7.5 13.5t3.5 4h2q1 -4 5 -10q2 -4 4.5 -7.5t6 -8t6.5 -8.5l107 -161h-76v-106h291v106h-68l-192 273l195 282h67v107h-279v-107h74l-103 -159q-4 -7 -10 -16.5t-9 -13.5l-2 -3h-2q-1 4 -5 10q-6 11 -17 23l-106 159h76v107 h-290v-107h68l189 -272l-194 -283h-68z" /> -<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M416 106v-106h327v106h-93v167h137q76 0 118 15q67 23 106.5 87t39.5 146q0 81 -37 141t-100 87q-48 19 -130 19h-368v-107h92v-555h-92zM769 386h-119v268h120q52 0 83 -18q56 -33 56 -115q0 -89 -62 -120q-31 -15 -78 -15z" /> -<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M1280 320v-320h-1024v192l192 192l128 -128l384 384zM448 512q-80 0 -136 56t-56 136t56 136t136 56t136 -56t56 -136t-56 -136t-136 -56z" /> -<glyph unicode="" d="M640 1152v128h-128v-128h128zM768 1024v128h-128v-128h128zM640 896v128h-128v-128h128zM768 768v128h-128v-128h128zM1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400 v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-128v-128h-128v128h-512v-1536h1280zM781 593l107 -349q8 -27 8 -52q0 -83 -72.5 -137.5t-183.5 -54.5t-183.5 54.5t-72.5 137.5q0 25 8 52q21 63 120 396v128h128v-128h79 q22 0 39 -13t23 -34zM640 128q53 0 90.5 19t37.5 45t-37.5 45t-90.5 19t-90.5 -19t-37.5 -45t37.5 -45t90.5 -19z" /> -<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M620 686q20 -8 20 -30v-544q0 -22 -20 -30q-8 -2 -12 -2q-12 0 -23 9l-166 167h-131q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h131l166 167q16 15 35 7zM1037 -3q31 0 50 24q129 159 129 363t-129 363q-16 21 -43 24t-47 -14q-21 -17 -23.5 -43.5t14.5 -47.5 q100 -123 100 -282t-100 -282q-17 -21 -14.5 -47.5t23.5 -42.5q18 -15 40 -15zM826 145q27 0 47 20q87 93 87 219t-87 219q-18 19 -45 20t-46 -17t-20 -44.5t18 -46.5q52 -57 52 -131t-52 -131q-19 -20 -18 -46.5t20 -44.5q20 -17 44 -17z" /> -<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M768 768q52 0 90 -38t38 -90v-384q0 -52 -38 -90t-90 -38h-384q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h384zM1260 766q20 -8 20 -30v-576q0 -22 -20 -30q-8 -2 -12 -2q-14 0 -23 9l-265 266v90l265 266q9 9 23 9q4 0 12 -2z" /> -<glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M480 768q8 11 21 12.5t24 -6.5l51 -38q11 -8 12.5 -21t-6.5 -24l-182 -243l182 -243q8 -11 6.5 -24t-12.5 -21l-51 -38q-11 -8 -24 -6.5t-21 12.5l-226 301q-14 19 0 38zM1282 467q14 -19 0 -38l-226 -301q-8 -11 -21 -12.5t-24 6.5l-51 38q-11 8 -12.5 21t6.5 24l182 243 l-182 243q-8 11 -6.5 24t12.5 21l51 38q11 8 24 6.5t21 -12.5zM662 6q-13 2 -20.5 13t-5.5 24l138 831q2 13 13 20.5t24 5.5l63 -10q13 -2 20.5 -13t5.5 -24l-138 -831q-2 -13 -13 -20.5t-24 -5.5z" /> -<glyph unicode="" d="M1497 709v-198q-101 -23 -198 -23q-65 -136 -165.5 -271t-181.5 -215.5t-128 -106.5q-80 -45 -162 3q-28 17 -60.5 43.5t-85 83.5t-102.5 128.5t-107.5 184t-105.5 244t-91.5 314.5t-70.5 390h283q26 -218 70 -398.5t104.5 -317t121.5 -235.5t140 -195q169 169 287 406 q-142 72 -223 220t-81 333q0 192 104 314.5t284 122.5q178 0 273 -105.5t95 -297.5q0 -159 -58 -286q-7 -1 -19.5 -3t-46 -2t-63 6t-62 25.5t-50.5 51.5q31 103 31 184q0 87 -29 132t-79 45q-53 0 -85 -49.5t-32 -140.5q0 -186 105 -293.5t267 -107.5q62 0 121 14z" /> -<glyph unicode="" horiz-adv-x="1792" d="M216 367l603 -402v359l-334 223zM154 511l193 129l-193 129v-258zM973 -35l603 402l-269 180l-334 -223v-359zM896 458l272 182l-272 182l-272 -182zM485 733l334 223v359l-603 -402zM1445 640l193 -129v258zM1307 733l269 180l-603 402v-359zM1792 913v-546 q0 -41 -34 -64l-819 -546q-21 -13 -43 -13t-43 13l-819 546q-34 23 -34 64v546q0 41 34 64l819 546q21 13 43 13t43 -13l819 -546q34 -23 34 -64z" /> -<glyph unicode="" horiz-adv-x="2048" d="M1800 764q111 -46 179.5 -145.5t68.5 -221.5q0 -164 -118 -280.5t-285 -116.5q-4 0 -11.5 0.5t-10.5 0.5h-1209h-1h-2h-5q-170 10 -288 125.5t-118 280.5q0 110 55 203t147 147q-12 39 -12 82q0 115 82 196t199 81q95 0 172 -58q75 154 222.5 248t326.5 94 q166 0 306 -80.5t221.5 -218.5t81.5 -301q0 -6 -0.5 -18t-0.5 -18zM468 498q0 -122 84 -193t208 -71q137 0 240 99q-16 20 -47.5 56.5t-43.5 50.5q-67 -65 -144 -65q-55 0 -93.5 33.5t-38.5 87.5q0 53 38.5 87t91.5 34q44 0 84.5 -21t73 -55t65 -75t69 -82t77 -75t97 -55 t121.5 -21q121 0 204.5 71.5t83.5 190.5q0 121 -84 192t-207 71q-143 0 -241 -97q14 -16 29.5 -34t34.5 -40t29 -34q66 64 142 64q52 0 92 -33t40 -84q0 -57 -37 -91.5t-94 -34.5q-43 0 -82.5 21t-72 55t-65.5 75t-69.5 82t-77.5 75t-96.5 55t-118.5 21q-122 0 -207 -70.5 t-85 -189.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 1408q-190 0 -361 -90l194 -194q82 28 167 28t167 -28l194 194q-171 90 -361 90zM218 279l194 194 q-28 82 -28 167t28 167l-194 194q-90 -171 -90 -361t90 -361zM896 -128q190 0 361 90l-194 194q-82 -28 -167 -28t-167 28l-194 -194q171 -90 361 -90zM896 256q159 0 271.5 112.5t112.5 271.5t-112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5 t271.5 -112.5zM1380 473l194 -194q90 171 90 361t-90 361l-194 -194q28 -82 28 -167t-28 -167z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348q0 222 101 414.5t276.5 317t390.5 155.5v-260q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 q0 230 -145.5 406t-366.5 221v260q215 -31 390.5 -155.5t276.5 -317t101 -414.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M19 662q8 217 116 406t305 318h5q0 -1 -1 -3q-8 -8 -28 -33.5t-52 -76.5t-60 -110.5t-44.5 -135.5t-14 -150.5t39 -157.5t108.5 -154q50 -50 102 -69.5t90.5 -11.5t69.5 23.5t47 32.5l16 16q39 51 53 116.5t6.5 122.5t-21 107t-26.5 80l-14 29q-10 25 -30.5 49.5t-43 41 t-43.5 29.5t-35 19l-13 6l104 115q39 -17 78 -52t59 -61l19 -27q1 48 -18.5 103.5t-40.5 87.5l-20 31l161 183l160 -181q-33 -46 -52.5 -102.5t-22.5 -90.5l-4 -33q22 37 61.5 72.5t67.5 52.5l28 17l103 -115q-44 -14 -85 -50t-60 -65l-19 -29q-31 -56 -48 -133.5t-7 -170 t57 -156.5q33 -45 77.5 -60.5t85 -5.5t76 26.5t57.5 33.5l21 16q60 53 96.5 115t48.5 121.5t10 121.5t-18 118t-37 107.5t-45.5 93t-45 72t-34.5 47.5l-13 17q-14 13 -7 13l10 -3q40 -29 62.5 -46t62 -50t64 -58t58.5 -65t55.5 -77t45.5 -88t38 -103t23.5 -117t10.5 -136 q3 -259 -108 -465t-312 -321t-456 -115q-185 0 -351 74t-283.5 198t-184 293t-60.5 353z" /> -<glyph unicode="" horiz-adv-x="1792" d="M874 -102v-66q-208 6 -385 109.5t-283 275.5l58 34q29 -49 73 -99l65 57q148 -168 368 -212l-17 -86q65 -12 121 -13zM276 428l-83 -28q22 -60 49 -112l-57 -33q-98 180 -98 385t98 385l57 -33q-30 -56 -49 -112l82 -28q-35 -100 -35 -212q0 -109 36 -212zM1528 251 l58 -34q-106 -172 -283 -275.5t-385 -109.5v66q56 1 121 13l-17 86q220 44 368 212l65 -57q44 50 73 99zM1377 805l-233 -80q14 -42 14 -85t-14 -85l232 -80q-31 -92 -98 -169l-185 162q-57 -67 -147 -85l48 -241q-52 -10 -98 -10t-98 10l48 241q-90 18 -147 85l-185 -162 q-67 77 -98 169l232 80q-14 42 -14 85t14 85l-233 80q33 93 99 169l185 -162q59 68 147 86l-48 240q44 10 98 10t98 -10l-48 -240q88 -18 147 -86l185 162q66 -76 99 -169zM874 1448v-66q-65 -2 -121 -13l17 -86q-220 -42 -368 -211l-65 56q-38 -42 -73 -98l-57 33 q106 172 282 275.5t385 109.5zM1705 640q0 -205 -98 -385l-57 33q27 52 49 112l-83 28q36 103 36 212q0 112 -35 212l82 28q-19 56 -49 112l57 33q98 -180 98 -385zM1585 1063l-57 -33q-35 56 -73 98l-65 -56q-148 169 -368 211l17 86q-56 11 -121 13v66q209 -6 385 -109.5 t282 -275.5zM1748 640q0 173 -67.5 331t-181.5 272t-272 181.5t-331 67.5t-331 -67.5t-272 -181.5t-181.5 -272t-67.5 -331t67.5 -331t181.5 -272t272 -181.5t331 -67.5t331 67.5t272 181.5t181.5 272t67.5 331zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71 t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" /> -<glyph unicode="" d="M582 228q0 -66 -93 -66q-107 0 -107 63q0 64 98 64q102 0 102 -61zM546 694q0 -85 -74 -85q-77 0 -77 84q0 90 77 90q36 0 55 -25.5t19 -63.5zM712 769v125q-78 -29 -135 -29q-50 29 -110 29q-86 0 -145 -57t-59 -143q0 -50 29.5 -102t73.5 -67v-3q-38 -17 -38 -85 q0 -53 41 -77v-3q-113 -37 -113 -139q0 -45 20 -78.5t54 -51t72 -25.5t81 -8q224 0 224 188q0 67 -48 99t-126 46q-27 5 -51.5 20.5t-24.5 39.5q0 44 49 52q77 15 122 70t45 134q0 24 -10 52q37 9 49 13zM771 350h137q-2 27 -2 82v387q0 46 2 69h-137q3 -23 3 -71v-392 q0 -50 -3 -75zM1280 366v121q-30 -21 -68 -21q-53 0 -53 82v225h52q9 0 26.5 -1t26.5 -1v117h-105q0 82 3 102h-140q4 -24 4 -55v-47h-60v-117q36 3 37 3q3 0 11 -0.5t12 -0.5v-2h-2v-217q0 -37 2.5 -64t11.5 -56.5t24.5 -48.5t43.5 -31t66 -12q64 0 108 24zM924 1072 q0 36 -24 63.5t-60 27.5t-60.5 -27t-24.5 -64q0 -36 25 -62.5t60 -26.5t59.5 27t24.5 62zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M595 22q0 100 -165 100q-158 0 -158 -104q0 -101 172 -101q151 0 151 105zM536 777q0 61 -30 102t-89 41q-124 0 -124 -145q0 -135 124 -135q119 0 119 137zM805 1101v-202q-36 -12 -79 -22q16 -43 16 -84q0 -127 -73 -216.5t-197 -112.5q-40 -8 -59.5 -27t-19.5 -58 q0 -31 22.5 -51.5t58 -32t78.5 -22t86 -25.5t78.5 -37.5t58 -64t22.5 -98.5q0 -304 -363 -304q-69 0 -130 12.5t-116 41t-87.5 82t-32.5 127.5q0 165 182 225v4q-67 41 -67 126q0 109 63 137v4q-72 24 -119.5 108.5t-47.5 165.5q0 139 95 231.5t235 92.5q96 0 178 -47 q98 0 218 47zM1123 220h-222q4 45 4 134v609q0 94 -4 128h222q-4 -33 -4 -124v-613q0 -89 4 -134zM1724 442v-196q-71 -39 -174 -39q-62 0 -107 20t-70 50t-39.5 78t-18.5 92t-4 103v351h2v4q-7 0 -19 1t-18 1q-21 0 -59 -6v190h96v76q0 54 -6 89h227q-6 -41 -6 -165h171 v-190q-15 0 -43.5 2t-42.5 2h-85v-365q0 -131 87 -131q61 0 109 33zM1148 1389q0 -58 -39 -101.5t-96 -43.5q-58 0 -98 43.5t-40 101.5q0 59 39.5 103t98.5 44q58 0 96.5 -44.5t38.5 -102.5z" /> -<glyph unicode="" d="M825 547l343 588h-150q-21 -39 -63.5 -118.5t-68 -128.5t-59.5 -118.5t-60 -128.5h-3q-21 48 -44.5 97t-52 105.5t-46.5 92t-54 104.5t-49 95h-150l323 -589v-435h134v436zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1280" d="M842 964q0 -80 -57 -136.5t-136 -56.5q-60 0 -111 35q-62 -67 -115 -146q-247 -371 -202 -859q1 -22 -12.5 -38.5t-34.5 -18.5h-5q-20 0 -35 13.5t-17 33.5q-14 126 -3.5 247.5t29.5 217t54 186t69 155.5t74 125q61 90 132 165q-16 35 -16 77q0 80 56.5 136.5t136.5 56.5 t136.5 -56.5t56.5 -136.5zM1223 953q0 -158 -78 -292t-212.5 -212t-292.5 -78q-64 0 -131 14q-21 5 -32.5 23.5t-6.5 39.5q5 20 23 31.5t39 7.5q51 -13 108 -13q97 0 186 38t153 102t102 153t38 186t-38 186t-102 153t-153 102t-186 38t-186 -38t-153 -102t-102 -153 t-38 -186q0 -114 52 -218q10 -20 3.5 -40t-25.5 -30t-39.5 -3t-30.5 26q-64 123 -64 265q0 119 46.5 227t124.5 186t186 124t226 46q158 0 292.5 -78t212.5 -212.5t78 -292.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M270 730q-8 19 -8 52q0 20 11 49t24 45q-1 22 7.5 53t22.5 43q0 139 92.5 288.5t217.5 209.5q139 66 324 66q133 0 266 -55q49 -21 90 -48t71 -56t55 -68t42 -74t32.5 -84.5t25.5 -89.5t22 -98l1 -5q55 -83 55 -150q0 -14 -9 -40t-9 -38q0 -1 1.5 -3.5t3.5 -5t2 -3.5 q77 -114 120.5 -214.5t43.5 -208.5q0 -43 -19.5 -100t-55.5 -57q-9 0 -19.5 7.5t-19 17.5t-19 26t-16 26.5t-13.5 26t-9 17.5q-1 1 -3 1l-5 -4q-59 -154 -132 -223q20 -20 61.5 -38.5t69 -41.5t35.5 -65q-2 -4 -4 -16t-7 -18q-64 -97 -302 -97q-53 0 -110.5 9t-98 20 t-104.5 30q-15 5 -23 7q-14 4 -46 4.5t-40 1.5q-41 -45 -127.5 -65t-168.5 -20q-35 0 -69 1.5t-93 9t-101 20.5t-74.5 40t-32.5 64q0 40 10 59.5t41 48.5q11 2 40.5 13t49.5 12q4 0 14 2q2 2 2 4l-2 3q-48 11 -108 105.5t-73 156.5l-5 3q-4 0 -12 -20q-18 -41 -54.5 -74.5 t-77.5 -37.5h-1q-4 0 -6 4.5t-5 5.5q-23 54 -23 100q0 275 252 466z" /> -<glyph unicode="" horiz-adv-x="2048" d="M580 1075q0 41 -25 66t-66 25q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 66 24.5t25 65.5zM1323 568q0 28 -25.5 50t-65.5 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q40 0 65.5 22t25.5 51zM1087 1075q0 41 -24.5 66t-65.5 25 q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 65.5 24.5t24.5 65.5zM1722 568q0 28 -26 50t-65 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q39 0 65 22t26 51zM1456 965q-31 4 -70 4q-169 0 -311 -77t-223.5 -208.5t-81.5 -287.5 q0 -78 23 -152q-35 -3 -68 -3q-26 0 -50 1.5t-55 6.5t-44.5 7t-54.5 10.5t-50 10.5l-253 -127l72 218q-290 203 -290 490q0 169 97.5 311t264 223.5t363.5 81.5q176 0 332.5 -66t262 -182.5t136.5 -260.5zM2048 404q0 -117 -68.5 -223.5t-185.5 -193.5l55 -181l-199 109 q-150 -37 -218 -37q-169 0 -311 70.5t-223.5 191.5t-81.5 264t81.5 264t223.5 191.5t311 70.5q161 0 303 -70.5t227.5 -192t85.5 -263.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1764 1525q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-453 185l-242 -295q-18 -23 -49 -23q-13 0 -22 4q-19 7 -30.5 23.5t-11.5 36.5v349l864 1059l-1069 -925l-395 162q-37 14 -40 55q-2 40 32 59l1664 960q15 9 32 9q20 0 36 -11z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1764 1525q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-527 215l-298 -327q-18 -21 -47 -21q-14 0 -23 4q-19 7 -30 23.5t-11 36.5v452l-472 193q-37 14 -40 55q-3 39 32 59l1664 960q35 21 68 -2zM1422 26l221 1323l-1434 -827l336 -137 l863 639l-478 -797z" /> -<glyph unicode="" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298zM896 928v-448q0 -14 -9 -23 t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23z" /> -<glyph unicode="" d="M768 1280q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1682 -128q-44 0 -132.5 3.5t-133.5 3.5q-44 0 -132 -3.5t-132 -3.5q-24 0 -37 20.5t-13 45.5q0 31 17 46t39 17t51 7t45 15q33 21 33 140l-1 391q0 21 -1 31q-13 4 -50 4h-675q-38 0 -51 -4q-1 -10 -1 -31l-1 -371q0 -142 37 -164q16 -10 48 -13t57 -3.5t45 -15 t20 -45.5q0 -26 -12.5 -48t-36.5 -22q-47 0 -139.5 3.5t-138.5 3.5q-43 0 -128 -3.5t-127 -3.5q-23 0 -35.5 21t-12.5 45q0 30 15.5 45t36 17.5t47.5 7.5t42 15q33 23 33 143l-1 57v813q0 3 0.5 26t0 36.5t-1.5 38.5t-3.5 42t-6.5 36.5t-11 31.5t-16 18q-15 10 -45 12t-53 2 t-41 14t-18 45q0 26 12 48t36 22q46 0 138.5 -3.5t138.5 -3.5q42 0 126.5 3.5t126.5 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17 -43.5t-38.5 -14.5t-49.5 -4t-43 -13q-35 -21 -35 -160l1 -320q0 -21 1 -32q13 -3 39 -3h699q25 0 38 3q1 11 1 32l1 320q0 139 -35 160 q-18 11 -58.5 12.5t-66 13t-25.5 49.5q0 26 12.5 48t37.5 22q44 0 132 -3.5t132 -3.5q43 0 129 3.5t129 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17.5 -44t-40 -14.5t-51.5 -3t-44 -12.5q-35 -23 -35 -161l1 -943q0 -119 34 -140q16 -10 46 -13.5t53.5 -4.5t41.5 -15.5t18 -44.5 q0 -26 -12 -48t-36 -22z" /> -<glyph unicode="" horiz-adv-x="1280" d="M1278 1347v-73q0 -29 -18.5 -61t-42.5 -32q-50 0 -54 -1q-26 -6 -32 -31q-3 -11 -3 -64v-1152q0 -25 -18 -43t-43 -18h-108q-25 0 -43 18t-18 43v1218h-143v-1218q0 -25 -17.5 -43t-43.5 -18h-108q-26 0 -43.5 18t-17.5 43v496q-147 12 -245 59q-126 58 -192 179 q-64 117 -64 259q0 166 88 286q88 118 209 159q111 37 417 37h479q25 0 43 -18t18 -43z" /> -<glyph unicode="" d="M352 128v-128h-352v128h352zM704 256q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM864 640v-128h-864v128h864zM224 1152v-128h-224v128h224zM1536 128v-128h-736v128h736zM576 1280q26 0 45 -19t19 -45v-256 q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM1216 768q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM1536 640v-128h-224v128h224zM1536 1152v-128h-864v128h864z" /> -<glyph unicode="" d="M1216 512q133 0 226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5t-226.5 93.5t-93.5 226.5q0 12 2 34l-360 180q-92 -86 -218 -86q-133 0 -226.5 93.5t-93.5 226.5t93.5 226.5t226.5 93.5q126 0 218 -86l360 180q-2 22 -2 34q0 133 93.5 226.5t226.5 93.5 t226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5q-126 0 -218 86l-360 -180q2 -22 2 -34t-2 -34l360 -180q92 86 218 86z" /> -<glyph unicode="" d="M1280 341q0 88 -62.5 151t-150.5 63q-84 0 -145 -58l-241 120q2 16 2 23t-2 23l241 120q61 -58 145 -58q88 0 150.5 63t62.5 151t-62.5 150.5t-150.5 62.5t-151 -62.5t-63 -150.5q0 -7 2 -23l-241 -120q-62 57 -145 57q-88 0 -150.5 -62.5t-62.5 -150.5t62.5 -150.5 t150.5 -62.5q83 0 145 57l241 -120q-2 -16 -2 -23q0 -88 63 -150.5t151 -62.5t150.5 62.5t62.5 150.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M571 947q-10 25 -34 35t-49 0q-108 -44 -191 -127t-127 -191q-10 -25 0 -49t35 -34q13 -5 24 -5q42 0 60 40q34 84 98.5 148.5t148.5 98.5q25 11 35 35t0 49zM1513 1303l46 -46l-244 -243l68 -68q19 -19 19 -45.5t-19 -45.5l-64 -64q89 -161 89 -343q0 -143 -55.5 -273.5 t-150 -225t-225 -150t-273.5 -55.5t-273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5q182 0 343 -89l64 64q19 19 45.5 19t45.5 -19l68 -68zM1521 1359q-10 -10 -22 -10q-13 0 -23 10l-91 90q-9 10 -9 23t9 23q10 9 23 9t23 -9l90 -91 q10 -9 10 -22.5t-10 -22.5zM1751 1129q-11 -9 -23 -9t-23 9l-90 91q-10 9 -10 22.5t10 22.5q9 10 22.5 10t22.5 -10l91 -90q9 -10 9 -23t-9 -23zM1792 1312q0 -14 -9 -23t-23 -9h-96q-14 0 -23 9t-9 23t9 23t23 9h96q14 0 23 -9t9 -23zM1600 1504v-96q0 -14 -9 -23t-23 -9 t-23 9t-9 23v96q0 14 9 23t23 9t23 -9t9 -23zM1751 1449l-91 -90q-10 -10 -22 -10q-13 0 -23 10q-10 9 -10 22.5t10 22.5l90 91q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" /> -<glyph unicode="" horiz-adv-x="1792" d="M609 720l287 208l287 -208l-109 -336h-355zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM1515 186q149 203 149 454v3l-102 -89l-240 224l63 323 l134 -12q-150 206 -389 282l53 -124l-287 -159l-287 159l53 124q-239 -76 -389 -282l135 12l62 -323l-240 -224l-102 89v-3q0 -251 149 -454l30 132l326 -40l139 -298l-116 -69q117 -39 240 -39t240 39l-116 69l139 298l326 40z" /> -<glyph unicode="" horiz-adv-x="1792" d="M448 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM256 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM832 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM66 768q-28 0 -47 19t-19 46v129h514v-129q0 -27 -19 -46t-46 -19h-383zM1216 224v-192q0 -14 -9 -23t-23 -9h-192 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1600 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23 zM1408 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1016v-13h-514v10q0 104 -382 102q-382 -1 -382 -102v-10h-514v13q0 17 8.5 43t34 64t65.5 75.5t110.5 76t160 67.5t224 47.5t293.5 18.5t293 -18.5t224 -47.5 t160.5 -67.5t110.5 -76t65.5 -75.5t34 -64t8.5 -43zM1792 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 962v-129q0 -27 -19 -46t-46 -19h-384q-27 0 -46 19t-19 46v129h514z" /> -<glyph unicode="" horiz-adv-x="1792" d="M704 1216v-768q0 -26 -19 -45t-45 -19v-576q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v512l249 873q7 23 31 23h424zM1024 1216v-704h-256v704h256zM1792 320v-512q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v576q-26 0 -45 19t-19 45v768h424q24 0 31 -23z M736 1504v-224h-352v224q0 14 9 23t23 9h288q14 0 23 -9t9 -23zM1408 1504v-224h-352v224q0 14 9 23t23 9h288q14 0 23 -9t9 -23z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1755 1083q37 -37 37 -90t-37 -91l-401 -400l150 -150l-160 -160q-163 -163 -389.5 -186.5t-411.5 100.5l-362 -362h-181v181l362 362q-124 185 -100.5 411.5t186.5 389.5l160 160l150 -150l400 401q38 37 91 37t90 -37t37 -90.5t-37 -90.5l-400 -401l234 -234l401 400 q38 37 91 37t90 -37z" /> -<glyph unicode="" horiz-adv-x="1792" d="M873 796q0 -83 -63.5 -142.5t-152.5 -59.5t-152.5 59.5t-63.5 142.5q0 84 63.5 143t152.5 59t152.5 -59t63.5 -143zM1375 796q0 -83 -63 -142.5t-153 -59.5q-89 0 -152.5 59.5t-63.5 142.5q0 84 63.5 143t152.5 59q90 0 153 -59t63 -143zM1600 616v667q0 87 -32 123.5 t-111 36.5h-1112q-83 0 -112.5 -34t-29.5 -126v-673q43 -23 88.5 -40t81 -28t81 -18.5t71 -11t70 -4t58.5 -0.5t56.5 2t44.5 2q68 1 95 -27q6 -6 10 -9q26 -25 61 -51q7 91 118 87q5 0 36.5 -1.5t43 -2t45.5 -1t53 1t54.5 4.5t61 8.5t62 13.5t67 19.5t67.5 27t72 34.5z M1763 621q-121 -149 -372 -252q84 -285 -23 -465q-66 -113 -183 -148q-104 -32 -182 15q-86 51 -82 164l-1 326v1q-8 2 -24.5 6t-23.5 5l-1 -338q4 -114 -83 -164q-79 -47 -183 -15q-117 36 -182 150q-105 180 -22 463q-251 103 -372 252q-25 37 -4 63t60 -1q3 -2 11 -7 t11 -8v694q0 72 47 123t114 51h1257q67 0 114 -51t47 -123v-694l21 15q39 27 60 1t-4 -63z" /> -<glyph unicode="" horiz-adv-x="1792" d="M896 1102v-434h-145v434h145zM1294 1102v-434h-145v434h145zM1294 342l253 254v795h-1194v-1049h326v-217l217 217h398zM1692 1536v-1013l-434 -434h-326l-217 -217h-217v217h-398v1158l109 289h1483z" /> -<glyph unicode="" d="M773 217v-127q-1 -292 -6 -305q-12 -32 -51 -40q-54 -9 -181.5 38t-162.5 89q-13 15 -17 36q-1 12 4 26q4 10 34 47t181 216q1 0 60 70q15 19 39.5 24.5t49.5 -3.5q24 -10 37.5 -29t12.5 -42zM624 468q-3 -55 -52 -70l-120 -39q-275 -88 -292 -88q-35 2 -54 36 q-12 25 -17 75q-8 76 1 166.5t30 124.5t56 32q13 0 202 -77q70 -29 115 -47l84 -34q23 -9 35.5 -30.5t11.5 -48.5zM1450 171q-7 -54 -91.5 -161t-135.5 -127q-37 -14 -63 7q-14 10 -184 287l-47 77q-14 21 -11.5 46t19.5 46q35 43 83 26q1 -1 119 -40q203 -66 242 -79.5 t47 -20.5q28 -22 22 -61zM778 803q5 -102 -54 -122q-58 -17 -114 71l-378 598q-8 35 19 62q41 43 207.5 89.5t224.5 31.5q40 -10 49 -45q3 -18 22 -305.5t24 -379.5zM1440 695q3 -39 -26 -59q-15 -10 -329 -86q-67 -15 -91 -23l1 2q-23 -6 -46 4t-37 32q-30 47 0 87 q1 1 75 102q125 171 150 204t34 39q28 19 65 2q48 -23 123 -133.5t81 -167.5v-3z" /> -<glyph unicode="" horiz-adv-x="2048" d="M1024 1024h-384v-384h384v384zM1152 384v-128h-640v128h640zM1152 1152v-640h-640v640h640zM1792 384v-128h-512v128h512zM1792 640v-128h-512v128h512zM1792 896v-128h-512v128h512zM1792 1152v-128h-512v128h512zM256 192v960h-128v-960q0 -26 19 -45t45 -19t45 19 t19 45zM1920 192v1088h-1536v-1088q0 -33 -11 -64h1483q26 0 45 19t19 45zM2048 1408v-1216q0 -80 -56 -136t-136 -56h-1664q-80 0 -136 56t-56 136v1088h256v128h1792z" /> -<glyph unicode="" horiz-adv-x="2048" d="M1024 13q-20 0 -93 73.5t-73 93.5q0 32 62.5 54t103.5 22t103.5 -22t62.5 -54q0 -20 -73 -93.5t-93 -73.5zM1294 284q-2 0 -40 25t-101.5 50t-128.5 25t-128.5 -25t-101 -50t-40.5 -25q-18 0 -93.5 75t-75.5 93q0 13 10 23q78 77 196 121t233 44t233 -44t196 -121 q10 -10 10 -23q0 -18 -75.5 -93t-93.5 -75zM1567 556q-11 0 -23 8q-136 105 -252 154.5t-268 49.5q-85 0 -170.5 -22t-149 -53t-113.5 -62t-79 -53t-31 -22q-17 0 -92 75t-75 93q0 12 10 22q132 132 320 205t380 73t380 -73t320 -205q10 -10 10 -22q0 -18 -75 -93t-92 -75z M1838 827q-11 0 -22 9q-179 157 -371.5 236.5t-420.5 79.5t-420.5 -79.5t-371.5 -236.5q-11 -9 -22 -9q-17 0 -92.5 75t-75.5 93q0 13 10 23q187 186 445 288t527 102t527 -102t445 -288q10 -10 10 -23q0 -18 -75.5 -93t-92.5 -75z" /> -<glyph unicode="" horiz-adv-x="1792" d="M384 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5 t37.5 90.5zM384 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 768q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1536 0v384q0 52 -38 90t-90 38t-90 -38t-38 -90v-384q0 -52 38 -90t90 -38t90 38t38 90zM1152 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5z M1536 1088v256q0 26 -19 45t-45 19h-1280q-26 0 -45 -19t-19 -45v-256q0 -26 19 -45t45 -19h1280q26 0 45 19t19 45zM1536 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1408v-1536q0 -52 -38 -90t-90 -38 h-1408q-52 0 -90 38t-38 90v1536q0 52 38 90t90 38h1408q52 0 90 -38t38 -90z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1112 1090q0 159 -237 159h-70q-32 0 -59.5 -21.5t-34.5 -52.5l-63 -276q-2 -5 -2 -16q0 -24 17 -39.5t41 -15.5h53q69 0 128.5 13t112.5 41t83.5 81.5t30.5 126.5zM1716 938q0 -265 -220 -428q-219 -161 -612 -161h-61q-32 0 -59 -21.5t-34 -52.5l-73 -316 q-8 -36 -40.5 -61.5t-69.5 -25.5h-213q-31 0 -53 20t-22 51q0 10 13 65h151q34 0 64 23.5t38 56.5l73 316q8 33 37.5 57t63.5 24h61q390 0 607 160t217 421q0 129 -51 207q183 -92 183 -335zM1533 1123q0 -264 -221 -428q-218 -161 -612 -161h-60q-32 0 -59.5 -22t-34.5 -53 l-73 -315q-8 -36 -40 -61.5t-69 -25.5h-214q-31 0 -52.5 19.5t-21.5 51.5q0 8 2 20l300 1301q8 36 40.5 61.5t69.5 25.5h444q68 0 125 -4t120.5 -15t113.5 -30t96.5 -50.5t77.5 -74t49.5 -103.5t18.5 -136z" /> -<glyph unicode="" horiz-adv-x="1792" d="M602 949q19 -61 31 -123.5t17 -141.5t-14 -159t-62 -145q-21 81 -67 157t-95.5 127t-99 90.5t-78.5 57.5t-33 19q-62 34 -81.5 100t14.5 128t101 81.5t129 -14.5q138 -83 238 -177zM927 1236q11 -25 20.5 -46t36.5 -100.5t42.5 -150.5t25.5 -179.5t0 -205.5t-47.5 -209.5 t-105.5 -208.5q-51 -72 -138 -72q-54 0 -98 31q-57 40 -69 109t28 127q60 85 81 195t13 199.5t-32 180.5t-39 128t-22 52q-31 63 -8.5 129.5t85.5 97.5q34 17 75 17q47 0 88.5 -25t63.5 -69zM1248 567q-17 -160 -72 -311q-17 131 -63 246q25 174 -5 361q-27 178 -94 342 q114 -90 212 -211q9 -37 15 -80q26 -179 7 -347zM1520 1440q9 -17 23.5 -49.5t43.5 -117.5t50.5 -178t34 -227.5t5 -269t-47 -300t-112.5 -323.5q-22 -48 -66 -75.5t-95 -27.5q-39 0 -74 16q-67 31 -92.5 100t4.5 136q58 126 90 257.5t37.5 239.5t-3.5 213.5t-26.5 180.5 t-38.5 138.5t-32.5 90t-15.5 32.5q-34 65 -11.5 135.5t87.5 104.5q37 20 81 20q49 0 91.5 -25.5t66.5 -70.5z" /> -<glyph unicode="" horiz-adv-x="2304" d="M1975 546h-138q14 37 66 179l3 9q4 10 10 26t9 26l12 -55zM531 611l-58 295q-11 54 -75 54h-268l-2 -13q311 -79 403 -336zM710 960l-162 -438l-17 89q-26 70 -85 129.5t-131 88.5l135 -510h175l261 641h-176zM849 318h166l104 642h-166zM1617 944q-69 27 -149 27 q-123 0 -201 -59t-79 -153q-1 -102 145 -174q48 -23 67 -41t19 -39q0 -30 -30 -46t-69 -16q-86 0 -156 33l-22 11l-23 -144q74 -34 185 -34q130 -1 208.5 59t80.5 160q0 106 -140 174q-49 25 -71 42t-22 38q0 22 24.5 38.5t70.5 16.5q70 1 124 -24l15 -8zM2042 960h-128 q-65 0 -87 -54l-246 -588h174l35 96h212q5 -22 20 -96h154zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> -<glyph unicode="" horiz-adv-x="2304" d="M671 603h-13q-47 0 -47 -32q0 -22 20 -22q17 0 28 15t12 39zM1066 639h62v3q1 4 0.5 6.5t-1 7t-2 8t-4.5 6.5t-7.5 5t-11.5 2q-28 0 -36 -38zM1606 603h-12q-48 0 -48 -32q0 -22 20 -22q17 0 28 15t12 39zM1925 629q0 41 -30 41q-19 0 -31 -20t-12 -51q0 -42 28 -42 q20 0 32.5 20t12.5 52zM480 770h87l-44 -262h-56l32 201l-71 -201h-39l-4 200l-34 -200h-53l44 262h81l2 -163zM733 663q0 -6 -4 -42q-16 -101 -17 -113h-47l1 22q-20 -26 -58 -26q-23 0 -37.5 16t-14.5 42q0 39 26 60.5t73 21.5q14 0 23 -1q0 3 0.5 5.5t1 4.5t0.5 3 q0 20 -36 20q-29 0 -59 -10q0 4 7 48q38 11 67 11q74 0 74 -62zM889 721l-8 -49q-22 3 -41 3q-27 0 -27 -17q0 -8 4.5 -12t21.5 -11q40 -19 40 -60q0 -72 -87 -71q-34 0 -58 6q0 2 7 49q29 -8 51 -8q32 0 32 19q0 7 -4.5 11.5t-21.5 12.5q-43 20 -43 59q0 72 84 72 q30 0 50 -4zM977 721h28l-7 -52h-29q-2 -17 -6.5 -40.5t-7 -38.5t-2.5 -18q0 -16 19 -16q8 0 16 2l-8 -47q-21 -7 -40 -7q-43 0 -45 47q0 12 8 56q3 20 25 146h55zM1180 648q0 -23 -7 -52h-111q-3 -22 10 -33t38 -11q30 0 58 14l-9 -54q-30 -8 -57 -8q-95 0 -95 95 q0 55 27.5 90.5t69.5 35.5q35 0 55.5 -21t20.5 -56zM1319 722q-13 -23 -22 -62q-22 2 -31 -24t-25 -128h-56l3 14q22 130 29 199h51l-3 -33q14 21 25.5 29.5t28.5 4.5zM1506 763l-9 -57q-28 14 -50 14q-31 0 -51 -27.5t-20 -70.5q0 -30 13.5 -47t38.5 -17q21 0 48 13 l-10 -59q-28 -8 -50 -8q-45 0 -71.5 30.5t-26.5 82.5q0 70 35.5 114.5t91.5 44.5q26 0 61 -13zM1668 663q0 -18 -4 -42q-13 -79 -17 -113h-46l1 22q-20 -26 -59 -26q-23 0 -37 16t-14 42q0 39 25.5 60.5t72.5 21.5q15 0 23 -1q2 7 2 13q0 20 -36 20q-29 0 -59 -10q0 4 8 48 q38 11 67 11q73 0 73 -62zM1809 722q-14 -24 -21 -62q-23 2 -31.5 -23t-25.5 -129h-56l3 14q19 104 29 199h52q0 -11 -4 -33q15 21 26.5 29.5t27.5 4.5zM1950 770h56l-43 -262h-53l3 19q-23 -23 -52 -23q-31 0 -49.5 24t-18.5 64q0 53 27.5 92t64.5 39q31 0 53 -29z M2061 640q0 148 -72.5 273t-198 198t-273.5 73q-181 0 -328 -110q127 -116 171 -284h-50q-44 150 -158 253q-114 -103 -158 -253h-50q44 168 171 284q-147 110 -328 110q-148 0 -273.5 -73t-198 -198t-72.5 -273t72.5 -273t198 -198t273.5 -73q181 0 328 110 q-120 111 -165 264h50q46 -138 152 -233q106 95 152 233h50q-45 -153 -165 -264q147 -110 328 -110q148 0 273.5 73t198 198t72.5 273zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> -<glyph unicode="" horiz-adv-x="2304" d="M313 759q0 -51 -36 -84q-29 -26 -89 -26h-17v220h17q61 0 89 -27q36 -31 36 -83zM2089 824q0 -52 -64 -52h-19v101h20q63 0 63 -49zM380 759q0 74 -50 120.5t-129 46.5h-95v-333h95q74 0 119 38q60 51 60 128zM410 593h65v333h-65v-333zM730 694q0 40 -20.5 62t-75.5 42 q-29 10 -39.5 19t-10.5 23q0 16 13.5 26.5t34.5 10.5q29 0 53 -27l34 44q-41 37 -98 37q-44 0 -74 -27.5t-30 -67.5q0 -35 18 -55.5t64 -36.5q37 -13 45 -19q19 -12 19 -34q0 -20 -14 -33.5t-36 -13.5q-48 0 -71 44l-42 -40q44 -64 115 -64q51 0 83 30.5t32 79.5zM1008 604 v77q-37 -37 -78 -37q-49 0 -80.5 32.5t-31.5 82.5q0 48 31.5 81.5t77.5 33.5q43 0 81 -38v77q-40 20 -80 20q-74 0 -125.5 -50.5t-51.5 -123.5t51 -123.5t125 -50.5q42 0 81 19zM2240 0v527q-65 -40 -144.5 -84t-237.5 -117t-329.5 -137.5t-417.5 -134.5t-504 -118h1569 q26 0 45 19t19 45zM1389 757q0 75 -53 128t-128 53t-128 -53t-53 -128t53 -128t128 -53t128 53t53 128zM1541 584l144 342h-71l-90 -224l-89 224h-71l142 -342h35zM1714 593h184v56h-119v90h115v56h-115v74h119v57h-184v-333zM2105 593h80l-105 140q76 16 76 94q0 47 -31 73 t-87 26h-97v-333h65v133h9zM2304 1274v-1268q0 -56 -38.5 -95t-93.5 -39h-2040q-55 0 -93.5 39t-38.5 95v1268q0 56 38.5 95t93.5 39h2040q55 0 93.5 -39t38.5 -95z" /> -<glyph unicode="" horiz-adv-x="2304" d="M119 854h89l-45 108zM740 328l74 79l-70 79h-163v-49h142v-55h-142v-54h159zM898 406l99 -110v217zM1186 453q0 33 -40 33h-84v-69h83q41 0 41 36zM1475 457q0 29 -42 29h-82v-61h81q43 0 43 32zM1197 923q0 29 -42 29h-82v-60h81q43 0 43 31zM1656 854h89l-44 108z M699 1009v-271h-66v212l-94 -212h-57l-94 212v-212h-132l-25 60h-135l-25 -60h-70l116 271h96l110 -257v257h106l85 -184l77 184h108zM1255 453q0 -20 -5.5 -35t-14 -25t-22.5 -16.5t-26 -10t-31.5 -4.5t-31.5 -1t-32.5 0.5t-29.5 0.5v-91h-126l-80 90l-83 -90h-256v271h260 l80 -89l82 89h207q109 0 109 -89zM964 794v-56h-217v271h217v-57h-152v-49h148v-55h-148v-54h152zM2304 235v-229q0 -55 -38.5 -94.5t-93.5 -39.5h-2040q-55 0 -93.5 39.5t-38.5 94.5v678h111l25 61h55l25 -61h218v46l19 -46h113l20 47v-47h541v99l10 1q10 0 10 -14v-86h279 v23q23 -12 55 -18t52.5 -6.5t63 0.5t51.5 1l25 61h56l25 -61h227v58l34 -58h182v378h-180v-44l-25 44h-185v-44l-23 44h-249q-69 0 -109 -22v22h-172v-22q-24 22 -73 22h-628l-43 -97l-43 97h-198v-44l-22 44h-169l-78 -179v391q0 55 38.5 94.5t93.5 39.5h2040 q55 0 93.5 -39.5t38.5 -94.5v-678h-120q-51 0 -81 -22v22h-177q-55 0 -78 -22v22h-316v-22q-31 22 -87 22h-209v-22q-23 22 -91 22h-234l-54 -58l-50 58h-349v-378h343l55 59l52 -59h211v89h21q59 0 90 13v-102h174v99h8q8 0 10 -2t2 -10v-87h529q57 0 88 24v-24h168 q60 0 95 17zM1546 469q0 -23 -12 -43t-34 -29q25 -9 34 -26t9 -46v-54h-65v45q0 33 -12 43.5t-46 10.5h-69v-99h-65v271h154q48 0 77 -15t29 -58zM1269 936q0 -24 -12.5 -44t-33.5 -29q26 -9 34.5 -25.5t8.5 -46.5v-53h-65q0 9 0.5 26.5t0 25t-3 18.5t-8.5 16t-17.5 8.5 t-29.5 3.5h-70v-98h-64v271l153 -1q49 0 78 -14.5t29 -57.5zM1798 327v-56h-216v271h216v-56h-151v-49h148v-55h-148v-54zM1372 1009v-271h-66v271h66zM2065 357q0 -86 -102 -86h-126v58h126q34 0 34 25q0 16 -17 21t-41.5 5t-49.5 3.5t-42 22.5t-17 55q0 39 26 60t66 21 h130v-57h-119q-36 0 -36 -25q0 -16 17.5 -20.5t42 -4t49 -2.5t42 -21.5t17.5 -54.5zM2304 407v-101q-24 -35 -88 -35h-125v58h125q33 0 33 25q0 13 -12.5 19t-31 5.5t-40 2t-40 8t-31 24t-12.5 48.5q0 39 26.5 60t66.5 21h129v-57h-118q-36 0 -36 -25q0 -20 29 -22t68.5 -5 t56.5 -26zM2139 1008v-270h-92l-122 203v-203h-132l-26 60h-134l-25 -60h-75q-129 0 -129 133q0 138 133 138h63v-59q-7 0 -28 1t-28.5 0.5t-23 -2t-21.5 -6.5t-14.5 -13.5t-11.5 -23t-3 -33.5q0 -38 13.5 -58t49.5 -20h29l92 213h97l109 -256v256h99l114 -188v188h66z" /> -<glyph unicode="" horiz-adv-x="2304" d="M322 689h-15q-19 0 -19 18q0 28 19 85q5 15 15 19.5t28 4.5q77 0 77 -49q0 -41 -30.5 -59.5t-74.5 -18.5zM664 528q-47 0 -47 29q0 62 123 62l3 -3q-5 -88 -79 -88zM1438 687h-15q-19 0 -19 19q0 28 19 85q5 15 14.5 19t28.5 4q77 0 77 -49q0 -41 -30.5 -59.5 t-74.5 -18.5zM1780 527q-47 0 -47 30q0 62 123 62l3 -3q-5 -89 -79 -89zM373 894h-128q-8 0 -14.5 -4t-8.5 -7.5t-7 -12.5q-3 -7 -45 -190t-42 -192q0 -7 5.5 -12.5t13.5 -5.5h62q25 0 32.5 34.5l15 69t32.5 34.5q47 0 87.5 7.5t80.5 24.5t63.5 52.5t23.5 84.5 q0 36 -14.5 61t-41 36.5t-53.5 15.5t-62 4zM719 798q-38 0 -74 -6q-2 0 -8.5 -1t-9 -1.5l-7.5 -1.5t-7.5 -2t-6.5 -3t-6.5 -4t-5 -5t-4.5 -7t-4 -9q-9 -29 -9 -39t9 -10q5 0 21.5 5t19.5 6q30 8 58 8q74 0 74 -36q0 -11 -10 -14q-8 -2 -18 -3t-21.5 -1.5t-17.5 -1.5 q-38 -4 -64.5 -10t-56.5 -19.5t-45.5 -39t-15.5 -62.5q0 -38 26 -59.5t64 -21.5q24 0 45.5 6.5t33 13t38.5 23.5q-3 -7 -3 -15t5.5 -13.5t12.5 -5.5h56q1 1 7 3.5t7.5 3.5t5 3.5t5 5.5t2.5 8l45 194q4 13 4 30q0 81 -145 81zM1247 793h-74q-22 0 -39 -23q-5 -7 -29.5 -51 t-46.5 -81.5t-26 -38.5l-5 4q0 77 -27 166q-1 5 -3.5 8.5t-6 6.5t-6.5 5t-8.5 3t-8.5 1.5t-9.5 1t-9 0.5h-10h-8.5q-38 0 -38 -21l1 -5q5 -53 25 -151t25 -143q2 -16 2 -24q0 -19 -30.5 -61.5t-30.5 -58.5q0 -13 40 -13q61 0 76 25l245 415q10 20 10 26q0 9 -8 9zM1489 892 h-129q-18 0 -29 -23q-6 -13 -46.5 -191.5t-40.5 -190.5q0 -20 43 -20h7.5h9h9t9.5 1t8.5 2t8.5 3t6.5 4.5t5.5 6t3 8.5l21 91q2 10 10.5 17t19.5 7q47 0 87.5 7t80.5 24.5t63.5 52.5t23.5 84q0 36 -14.5 61t-41 36.5t-53.5 15.5t-62 4zM1835 798q-26 0 -74 -6 q-38 -6 -48 -16q-7 -8 -11 -19q-8 -24 -8 -39q0 -10 8 -10q1 0 41 12q30 8 58 8q74 0 74 -36q0 -12 -10 -14q-4 -1 -57 -7q-38 -4 -64.5 -10t-56.5 -19.5t-45.5 -39t-15.5 -62.5t26 -58.5t64 -21.5q24 0 45 6t34 13t38 24q-3 -15 -3 -16q0 -5 2 -8.5t6.5 -5.5t8 -3.5 t10.5 -2t9.5 -0.5h9.5h8q42 0 48 25l45 194q3 15 3 31q0 81 -145 81zM2157 889h-55q-25 0 -33 -40q-10 -44 -36.5 -167t-42.5 -190v-5q0 -16 16 -18h1h57q10 0 18.5 6.5t10.5 16.5l83 374h-1l1 5q0 7 -5.5 12.5t-13.5 5.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048 q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> -<glyph unicode="" horiz-adv-x="2304" d="M1597 633q0 -69 -21 -106q-19 -35 -52 -35q-23 0 -41 9v224q29 30 57 30q57 0 57 -122zM2035 669h-110q6 98 56 98q51 0 54 -98zM476 534q0 59 -33 91.5t-101 57.5q-36 13 -52 24t-16 25q0 26 38 26q58 0 124 -33l18 112q-67 32 -149 32q-77 0 -123 -38q-48 -39 -48 -109 q0 -58 32.5 -90.5t99.5 -56.5q39 -14 54.5 -25.5t15.5 -27.5q0 -31 -48 -31q-29 0 -70 12.5t-72 30.5l-18 -113q72 -41 168 -41q81 0 129 37q51 41 51 117zM771 749l19 111h-96v135l-129 -21l-18 -114l-46 -8l-17 -103h62v-219q0 -84 44 -120q38 -30 111 -30q32 0 79 11v118 q-32 -7 -44 -7q-42 0 -42 50v197h77zM1087 724v139q-15 3 -28 3q-32 0 -55.5 -16t-33.5 -46l-10 56h-131v-471h150v306q26 31 82 31q16 0 26 -2zM1124 389h150v471h-150v-471zM1746 638q0 122 -45 179q-40 52 -111 52q-64 0 -117 -56l-8 47h-132v-645l150 25v151 q36 -11 68 -11q83 0 134 56q61 65 61 202zM1278 986q0 33 -23 56t-56 23t-56 -23t-23 -56t23 -56.5t56 -23.5t56 23.5t23 56.5zM2176 629q0 113 -48 176q-50 64 -144 64q-96 0 -151.5 -66t-55.5 -180q0 -128 63 -188q55 -55 161 -55q101 0 160 40l-16 103q-57 -31 -128 -31 q-43 0 -63 19q-23 19 -28 66h248q2 14 2 52zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> -<glyph unicode="" horiz-adv-x="2048" d="M1558 684q61 -356 298 -556q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5zM1024 -176q16 0 16 16t-16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5zM2026 1424q8 -10 7.5 -23.5t-10.5 -22.5 l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5 l418 363q10 8 23.5 7t21.5 -11z" /> -<glyph unicode="" horiz-adv-x="2048" d="M1040 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM503 315l877 760q-42 88 -132.5 146.5t-223.5 58.5q-93 0 -169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -384 -137 -645zM1856 128 q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5l149 129h757q-166 187 -227 459l111 97q61 -356 298 -556zM1942 1520l84 -96q8 -10 7.5 -23.5t-10.5 -22.5l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161 q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5l418 363q10 8 23.5 7t21.5 -11z" /> -<glyph unicode="" horiz-adv-x="1408" d="M512 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM768 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1024 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704 q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167 q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" /> -<glyph unicode="" d="M1150 462v-109q0 -50 -36.5 -89t-94 -60.5t-118 -32.5t-117.5 -11q-205 0 -342.5 139t-137.5 346q0 203 136 339t339 136q34 0 75.5 -4.5t93 -18t92.5 -34t69 -56.5t28 -81v-109q0 -16 -16 -16h-118q-16 0 -16 16v70q0 43 -65.5 67.5t-137.5 24.5q-140 0 -228.5 -91.5 t-88.5 -237.5q0 -151 91.5 -249.5t233.5 -98.5q68 0 138 24t70 66v70q0 7 4.5 11.5t10.5 4.5h119q6 0 11 -4.5t5 -11.5zM768 1280q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M972 761q0 108 -53.5 169t-147.5 61q-63 0 -124 -30.5t-110 -84.5t-79.5 -137t-30.5 -180q0 -112 53.5 -173t150.5 -61q96 0 176 66.5t122.5 166t42.5 203.5zM1536 640q0 -111 -37 -197t-98.5 -135t-131.5 -74.5t-145 -27.5q-6 0 -15.5 -0.5t-16.5 -0.5q-95 0 -142 53 q-28 33 -33 83q-52 -66 -131.5 -110t-173.5 -44q-161 0 -249.5 95.5t-88.5 269.5q0 157 66 290t179 210.5t246 77.5q87 0 155 -35.5t106 -99.5l2 19l11 56q1 6 5.5 12t9.5 6h118q5 0 13 -11q5 -5 3 -16l-120 -614q-5 -24 -5 -48q0 -39 12.5 -52t44.5 -13q28 1 57 5.5t73 24 t77 50t57 89.5t24 137q0 292 -174 466t-466 174q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51q228 0 405 144q11 9 24 8t21 -12l41 -49q8 -12 7 -24q-2 -13 -12 -22q-102 -83 -227.5 -128t-258.5 -45q-156 0 -298 61 t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q344 0 556 -212t212 -556z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1698 1442q94 -94 94 -226.5t-94 -225.5l-225 -223l104 -104q10 -10 10 -23t-10 -23l-210 -210q-10 -10 -23 -10t-23 10l-105 105l-603 -603q-37 -37 -90 -37h-203l-256 -128l-64 64l128 256v203q0 53 37 90l603 603l-105 105q-10 10 -10 23t10 23l210 210q10 10 23 10 t23 -10l104 -104l223 225q93 94 225.5 94t226.5 -94zM512 64l576 576l-192 192l-576 -576v-192h192z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1615 1536q70 0 122.5 -46.5t52.5 -116.5q0 -63 -45 -151q-332 -629 -465 -752q-97 -91 -218 -91q-126 0 -216.5 92.5t-90.5 219.5q0 128 92 212l638 579q59 54 130 54zM706 502q39 -76 106.5 -130t150.5 -76l1 -71q4 -213 -129.5 -347t-348.5 -134q-123 0 -218 46.5 t-152.5 127.5t-86.5 183t-29 220q7 -5 41 -30t62 -44.5t59 -36.5t46 -17q41 0 55 37q25 66 57.5 112.5t69.5 76t88 47.5t103 25.5t125 10.5z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 128v-384h-1792v384q45 0 85 14t59 27.5t47 37.5q30 27 51.5 38t56.5 11t55.5 -11t52.5 -38q29 -25 47 -38t58 -27t86 -14q45 0 85 14.5t58 27t48 37.5q21 19 32.5 27t31 15t43.5 7q35 0 56.5 -11t51.5 -38q28 -24 47 -37.5t59 -27.5t85 -14t85 14t59 27.5t47 37.5 q30 27 51.5 38t56.5 11q34 0 55.5 -11t51.5 -38q28 -24 47 -37.5t59 -27.5t85 -14zM1792 448v-192q-35 0 -55.5 11t-52.5 38q-29 25 -47 38t-58 27t-85 14q-46 0 -86 -14t-58 -27t-47 -38q-22 -19 -33 -27t-31 -15t-44 -7q-35 0 -56.5 11t-51.5 38q-29 25 -47 38t-58 27 t-86 14q-45 0 -85 -14.5t-58 -27t-48 -37.5q-21 -19 -32.5 -27t-31 -15t-43.5 -7q-35 0 -56.5 11t-51.5 38q-28 24 -47 37.5t-59 27.5t-85 14q-46 0 -86 -14t-58 -27t-47 -38q-30 -27 -51.5 -38t-56.5 -11v192q0 80 56 136t136 56h64v448h256v-448h256v448h256v-448h256v448 h256v-448h64q80 0 136 -56t56 -136zM512 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150zM1024 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51 t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150zM1536 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150z" /> -<glyph unicode="" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1664 1024l256 -896h-1664v576l448 576l576 -576z" /> -<glyph unicode="" horiz-adv-x="1792" d="M768 646l546 -546q-106 -108 -247.5 -168t-298.5 -60q-209 0 -385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103v-762zM955 640h773q0 -157 -60 -298.5t-168 -247.5zM1664 768h-768v768q209 0 385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1920 1248v-435q0 -21 -19.5 -29.5t-35.5 7.5l-121 121l-633 -633q-10 -10 -23 -10t-23 10l-233 233l-416 -416l-192 192l585 585q10 10 23 10t23 -10l233 -233l464 464l-121 121q-16 16 -7.5 35.5t29.5 19.5h435q14 0 23 -9 t9 -23z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1292 832q0 -6 10 -41q10 -29 25 -49.5t41 -34t44 -20t55 -16.5q325 -91 325 -332q0 -146 -105.5 -242.5t-254.5 -96.5q-59 0 -111.5 18.5t-91.5 45.5t-77 74.5t-63 87.5t-53.5 103.5t-43.5 103t-39.5 106.5t-35.5 95q-32 81 -61.5 133.5t-73.5 96.5t-104 64t-142 20 q-96 0 -183 -55.5t-138 -144.5t-51 -185q0 -160 106.5 -279.5t263.5 -119.5q177 0 258 95q56 63 83 116l84 -152q-15 -34 -44 -70l1 -1q-131 -152 -388 -152q-147 0 -269.5 79t-190.5 207.5t-68 274.5q0 105 43.5 206t116 176.5t172 121.5t204.5 46q87 0 159 -19t123.5 -50 t95 -80t72.5 -99t58.5 -117t50.5 -124.5t50 -130.5t55 -127q96 -200 233 -200q81 0 138.5 48.5t57.5 128.5q0 42 -19 72t-50.5 46t-72.5 31.5t-84.5 27t-87.5 34t-81 52t-65 82t-39 122.5q-3 16 -3 33q0 110 87.5 192t198.5 78q78 -3 120.5 -14.5t90.5 -53.5h-1 q12 -11 23 -24.5t26 -36t19 -27.5l-129 -99q-26 49 -54 70v1q-23 21 -97 21q-49 0 -84 -33t-35 -83z" /> -<glyph unicode="" d="M1432 484q0 173 -234 239q-35 10 -53 16.5t-38 25t-29 46.5q0 2 -2 8.5t-3 12t-1 7.5q0 36 24.5 59.5t60.5 23.5q54 0 71 -15h-1q20 -15 39 -51l93 71q-39 54 -49 64q-33 29 -67.5 39t-85.5 10q-80 0 -142 -57.5t-62 -137.5q0 -7 2 -23q16 -96 64.5 -140t148.5 -73 q29 -8 49 -15.5t45 -21.5t38.5 -34.5t13.5 -46.5v-5q1 -58 -40.5 -93t-100.5 -35q-97 0 -167 144q-23 47 -51.5 121.5t-48 125.5t-54 110.5t-74 95.5t-103.5 60.5t-147 24.5q-101 0 -192 -56t-144 -148t-50 -192v-1q4 -108 50.5 -199t133.5 -147.5t196 -56.5q186 0 279 110 q20 27 31 51l-60 109q-42 -80 -99 -116t-146 -36q-115 0 -191 87t-76 204q0 105 82 189t186 84q112 0 170 -53.5t104 -172.5q8 -21 25.5 -68.5t28.5 -76.5t31.5 -74.5t38.5 -74t45.5 -62.5t55.5 -53.5t66 -33t80 -13.5q107 0 183 69.5t76 174.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="2048" d="M1152 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1920 640q0 104 -40.5 198.5 t-109.5 163.5t-163.5 109.5t-198.5 40.5h-386q119 -90 188.5 -224t69.5 -288t-69.5 -288t-188.5 -224h386q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM2048 640q0 -130 -51 -248.5t-136.5 -204t-204 -136.5t-248.5 -51h-768q-130 0 -248.5 51t-204 136.5 t-136.5 204t-51 248.5t51 248.5t136.5 204t204 136.5t248.5 51h768q130 0 248.5 -51t204 -136.5t136.5 -204t51 -248.5z" /> -<glyph unicode="" horiz-adv-x="2048" d="M0 640q0 130 51 248.5t136.5 204t204 136.5t248.5 51h768q130 0 248.5 -51t204 -136.5t136.5 -204t51 -248.5t-51 -248.5t-136.5 -204t-204 -136.5t-248.5 -51h-768q-130 0 -248.5 51t-204 136.5t-136.5 204t-51 248.5zM1408 128q104 0 198.5 40.5t163.5 109.5 t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5z" /> -<glyph unicode="" horiz-adv-x="2304" d="M762 384h-314q-40 0 -57.5 35t6.5 67l188 251q-65 31 -137 31q-132 0 -226 -94t-94 -226t94 -226t226 -94q115 0 203 72.5t111 183.5zM576 512h186q-18 85 -75 148zM1056 512l288 384h-480l-99 -132q105 -103 126 -252h165zM2176 448q0 132 -94 226t-226 94 q-60 0 -121 -24l174 -260q15 -23 10 -49t-27 -40q-15 -11 -36 -11q-35 0 -53 29l-174 260q-93 -95 -93 -225q0 -132 94 -226t226 -94t226 94t94 226zM2304 448q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 97 39.5 183.5t109.5 149.5l-65 98l-353 -469 q-18 -26 -51 -26h-197q-23 -164 -149 -274t-294 -110q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q114 0 215 -55l137 183h-224q-26 0 -45 19t-19 45t19 45t45 19h384v-128h435l-85 128h-222q-26 0 -45 19t-19 45t19 45t45 19h256q33 0 53 -28l267 -400 q91 44 192 44q185 0 316.5 -131.5t131.5 -316.5z" /> -<glyph unicode="" d="M384 320q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1408 320q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1362 716l-72 384q-5 23 -22.5 37.5t-40.5 14.5 h-918q-23 0 -40.5 -14.5t-22.5 -37.5l-72 -384q-5 -30 14 -53t49 -23h1062q30 0 49 23t14 53zM1136 1328q0 20 -14 34t-34 14h-640q-20 0 -34 -14t-14 -34t14 -34t34 -14h640q20 0 34 14t14 34zM1536 603v-603h-128v-128q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5v128h-768v-128q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5v128h-128v603q0 112 25 223l103 454q9 78 97.5 137t230 89t312.5 30t312.5 -30t230 -89t97.5 -137l105 -454q23 -102 23 -223z" /> -<glyph unicode="" horiz-adv-x="2048" d="M1463 704q0 -35 -25 -60.5t-61 -25.5h-702q-36 0 -61 25.5t-25 60.5t25 60.5t61 25.5h702q36 0 61 -25.5t25 -60.5zM1677 704q0 86 -23 170h-982q-36 0 -61 25t-25 60q0 36 25 61t61 25h908q-88 143 -235 227t-320 84q-177 0 -327.5 -87.5t-238 -237.5t-87.5 -327 q0 -86 23 -170h982q36 0 61 -25t25 -60q0 -36 -25 -61t-61 -25h-908q88 -143 235.5 -227t320.5 -84q132 0 253 51.5t208 139t139 208t52 253.5zM2048 959q0 -35 -25 -60t-61 -25h-131q17 -85 17 -170q0 -167 -65.5 -319.5t-175.5 -263t-262.5 -176t-319.5 -65.5 q-246 0 -448.5 133t-301.5 350h-189q-36 0 -61 25t-25 61q0 35 25 60t61 25h132q-17 85 -17 170q0 167 65.5 319.5t175.5 263t262.5 176t320.5 65.5q245 0 447.5 -133t301.5 -350h188q36 0 61 -25t25 -61z" /> -<glyph unicode="" horiz-adv-x="1280" d="M953 1158l-114 -328l117 -21q165 451 165 518q0 56 -38 56q-57 0 -130 -225zM654 471l33 -88q37 42 71 67l-33 5.5t-38.5 7t-32.5 8.5zM362 1367q0 -98 159 -521q18 10 49 10q15 0 75 -5l-121 351q-75 220 -123 220q-19 0 -29 -17.5t-10 -37.5zM283 608q0 -36 51.5 -119 t117.5 -153t100 -70q14 0 25.5 13t11.5 27q0 24 -32 102q-13 32 -32 72t-47.5 89t-61.5 81t-62 32q-20 0 -45.5 -27t-25.5 -47zM125 273q0 -41 25 -104q59 -145 183.5 -227t281.5 -82q227 0 382 170q152 169 152 427q0 43 -1 67t-11.5 62t-30.5 56q-56 49 -211.5 75.5 t-270.5 26.5q-37 0 -49 -11q-12 -5 -12 -35q0 -34 21.5 -60t55.5 -40t77.5 -23.5t87.5 -11.5t85 -4t70 0h23q24 0 40 -19q15 -19 19 -55q-28 -28 -96 -54q-61 -22 -93 -46q-64 -46 -108.5 -114t-44.5 -137q0 -31 18.5 -88.5t18.5 -87.5l-3 -12q-4 -12 -4 -14 q-137 10 -146 216q-8 -2 -41 -2q2 -7 2 -21q0 -53 -40.5 -89.5t-94.5 -36.5q-82 0 -166.5 78t-84.5 159q0 34 33 67q52 -64 60 -76q77 -104 133 -104q12 0 26.5 8.5t14.5 20.5q0 34 -87.5 145t-116.5 111q-43 0 -70 -44.5t-27 -90.5zM11 264q0 101 42.5 163t136.5 88 q-28 74 -28 104q0 62 61 123t122 61q29 0 70 -15q-163 462 -163 567q0 80 41 130.5t119 50.5q131 0 325 -581q6 -17 8 -23q6 16 29 79.5t43.5 118.5t54 127.5t64.5 123t70.5 86.5t76.5 36q71 0 112 -49t41 -122q0 -108 -159 -550q61 -15 100.5 -46t58.5 -78t26 -93.5 t7 -110.5q0 -150 -47 -280t-132 -225t-211 -150t-278 -55q-111 0 -223 42q-149 57 -258 191.5t-109 286.5z" /> -<glyph unicode="" horiz-adv-x="2048" d="M785 528h207q-14 -158 -98.5 -248.5t-214.5 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-203q-5 64 -35.5 99t-81.5 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t40 -51.5t66 -18q95 0 109 139zM1497 528h206 q-14 -158 -98 -248.5t-214 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-204q-4 64 -35 99t-81 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t39.5 -51.5t65.5 -18q49 0 76.5 38t33.5 101zM1856 647q0 207 -15.5 307 t-60.5 161q-6 8 -13.5 14t-21.5 15t-16 11q-86 63 -697 63q-625 0 -710 -63q-5 -4 -17.5 -11.5t-21 -14t-14.5 -14.5q-45 -60 -60 -159.5t-15 -308.5q0 -208 15 -307.5t60 -160.5q6 -8 15 -15t20.5 -14t17.5 -12q44 -33 239.5 -49t470.5 -16q610 0 697 65q5 4 17 11t20.5 14 t13.5 16q46 60 61 159t15 309zM2048 1408v-1536h-2048v1536h2048z" /> -<glyph unicode="" d="M992 912v-496q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v496q0 112 -80 192t-192 80h-272v-1152q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v1344q0 14 9 23t23 9h464q135 0 249 -66.5t180.5 -180.5t66.5 -249zM1376 1376v-880q0 -135 -66.5 -249t-180.5 -180.5 t-249 -66.5h-464q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h160q14 0 23 -9t9 -23v-768h272q112 0 192 80t80 192v880q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" /> -<glyph unicode="" d="M1311 694v-114q0 -24 -13.5 -38t-37.5 -14h-202q-24 0 -38 14t-14 38v114q0 24 14 38t38 14h202q24 0 37.5 -14t13.5 -38zM821 464v250q0 53 -32.5 85.5t-85.5 32.5h-133q-68 0 -96 -52q-28 52 -96 52h-130q-53 0 -85.5 -32.5t-32.5 -85.5v-250q0 -22 21 -22h55 q22 0 22 22v230q0 24 13.5 38t38.5 14h94q24 0 38 -14t14 -38v-230q0 -22 21 -22h54q22 0 22 22v230q0 24 14 38t38 14h97q24 0 37.5 -14t13.5 -38v-230q0 -22 22 -22h55q21 0 21 22zM1410 560v154q0 53 -33 85.5t-86 32.5h-264q-53 0 -86 -32.5t-33 -85.5v-410 q0 -21 22 -21h55q21 0 21 21v180q31 -42 94 -42h191q53 0 86 32.5t33 85.5zM1536 1176v-1072q0 -96 -68 -164t-164 -68h-1072q-96 0 -164 68t-68 164v1072q0 96 68 164t164 68h1072q96 0 164 -68t68 -164z" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> -</font> -</defs></svg>
\ No newline at end of file diff --git a/mitmproxy/webfonts/fontawesome-webfont.ttf b/mitmproxy/webfonts/fontawesome-webfont.ttf Binary files differdeleted file mode 100644 index 96a3639c..00000000 --- a/mitmproxy/webfonts/fontawesome-webfont.ttf +++ /dev/null diff --git a/mitmproxy/webfonts/fontawesome-webfont.woff b/mitmproxy/webfonts/fontawesome-webfont.woff Binary files differdeleted file mode 100644 index 628b6a52..00000000 --- a/mitmproxy/webfonts/fontawesome-webfont.woff +++ /dev/null diff --git a/netlib/basetypes.py b/netlib/basetypes.py new file mode 100644 index 00000000..9d6c60ba --- /dev/null +++ b/netlib/basetypes.py @@ -0,0 +1,34 @@ +import six +import abc + + +@six.add_metaclass(abc.ABCMeta) +class Serializable(object): + """ + Abstract Base Class that defines an API to save an object's state and restore it later on. + """ + + @classmethod + @abc.abstractmethod + def from_state(cls, state): + """ + Create a new object from the given state. + """ + raise NotImplementedError() + + @abc.abstractmethod + def get_state(self): + """ + Retrieve object state. + """ + raise NotImplementedError() + + @abc.abstractmethod + def set_state(self, state): + """ + Set object state to the given state. + """ + raise NotImplementedError() + + def copy(self): + return self.from_state(self.get_state()) diff --git a/netlib/certutils.py b/netlib/certutils.py index 34e01ed3..9eb41d03 100644 --- a/netlib/certutils.py +++ b/netlib/certutils.py @@ -12,7 +12,7 @@ from pyasn1.codec.der.decoder import decode from pyasn1.error import PyAsn1Error import OpenSSL -from .utils import Serializable +from netlib import basetypes # Default expiry must not be too long: https://github.com/mitmproxy/mitmproxy/issues/815 @@ -364,7 +364,7 @@ class _GeneralNames(univ.SequenceOf): constraint.ValueSizeConstraint(1, 1024) -class SSLCert(Serializable): +class SSLCert(basetypes.Serializable): def __init__(self, cert): """ diff --git a/netlib/http/__init__.py b/netlib/http/__init__.py index c4eb1d58..af95f4d0 100644 --- a/netlib/http/__init__.py +++ b/netlib/http/__init__.py @@ -1,14 +1,14 @@ from __future__ import absolute_import, print_function, division -from .request import Request -from .response import Response -from .headers import Headers -from .message import decoded -from . import http1, http2, status_codes +from netlib.http.request import Request +from netlib.http.response import Response +from netlib.http.headers import Headers, parse_content_type +from netlib.http.message import decoded +from netlib.http import http1, http2, status_codes, multipart __all__ = [ "Request", "Response", - "Headers", + "Headers", "parse_content_type", "decoded", - "http1", "http2", "status_codes", + "http1", "http2", "status_codes", "multipart", ] diff --git a/netlib/http/authentication.py b/netlib/http/authentication.py index 6db70fdd..38ea46d6 100644 --- a/netlib/http/authentication.py +++ b/netlib/http/authentication.py @@ -1,5 +1,5 @@ from __future__ import (absolute_import, print_function, division) -from argparse import Action, ArgumentTypeError +import argparse import binascii @@ -124,7 +124,7 @@ class PassManSingleUser(PassMan): return self.username == username and self.password == password_token -class AuthAction(Action): +class AuthAction(argparse.Action): """ Helper class to allow seamless integration int argparse. Example usage: @@ -148,7 +148,7 @@ class SingleuserAuthAction(AuthAction): def getPasswordManager(self, s): if len(s.split(':')) != 2: - raise ArgumentTypeError( + raise argparse.ArgumentTypeError( "Invalid single-user specification. Please use the format username:password" ) username, password = s.split(':') diff --git a/netlib/http/cookies.py b/netlib/http/cookies.py index 88c76870..768a85df 100644 --- a/netlib/http/cookies.py +++ b/netlib/http/cookies.py @@ -1,9 +1,8 @@ import collections import re -from email.utils import parsedate_tz, formatdate, mktime_tz -from netlib.multidict import ImmutableMultiDict -from .. import odict +import email.utils +from netlib import multidict """ A flexible module for cookie parsing and manipulation. @@ -28,6 +27,7 @@ variants. Serialization follows RFC6265. # TODO: Disallow LHS-only Cookie values + def _read_until(s, start, term): """ Read until one of the characters in term is reached. @@ -167,7 +167,7 @@ def parse_set_cookie_headers(headers): return ret -class CookieAttrs(ImmutableMultiDict): +class CookieAttrs(multidict.ImmutableMultiDict): @staticmethod def _kconv(key): return key.lower() @@ -243,10 +243,10 @@ def refresh_set_cookie_header(c, delta): raise ValueError("Invalid Cookie") if "expires" in attrs: - e = parsedate_tz(attrs["expires"]) + e = email.utils.parsedate_tz(attrs["expires"]) if e: - f = mktime_tz(e) + delta - attrs = attrs.with_set_all("expires", [formatdate(f)]) + f = email.utils.mktime_tz(e) + delta + attrs = attrs.with_set_all("expires", [email.utils.formatdate(f)]) else: # This can happen when the expires tag is invalid. # reddit.com sends a an expires tag like this: "Thu, 31 Dec diff --git a/netlib/http/headers.py b/netlib/http/headers.py index 60d3f429..14888ea9 100644 --- a/netlib/http/headers.py +++ b/netlib/http/headers.py @@ -2,27 +2,28 @@ from __future__ import absolute_import, print_function, division import re -try: - from collections.abc import MutableMapping -except ImportError: # pragma: no cover - from collections import MutableMapping # Workaround for Python < 3.3 - import six -from ..multidict import MultiDict -from ..utils import always_bytes +from netlib import multidict +from netlib import strutils # See also: http://lucumr.pocoo.org/2013/7/2/the-updated-guide-to-unicode/ if six.PY2: # pragma: no cover - _native = lambda x: x - _always_bytes = lambda x: x + def _native(x): + return x + + def _always_bytes(x): + return x else: # While headers _should_ be ASCII, it's not uncommon for certain headers to be utf-8 encoded. - _native = lambda x: x.decode("utf-8", "surrogateescape") - _always_bytes = lambda x: always_bytes(x, "utf-8", "surrogateescape") + def _native(x): + return x.decode("utf-8", "surrogateescape") + + def _always_bytes(x): + return strutils.always_bytes(x, "utf-8", "surrogateescape") -class Headers(MultiDict): +class Headers(multidict.MultiDict): """ Header class which allows both convenient access to individual headers as well as direct access to the underlying raw data. Provides a full dictionary interface. @@ -70,7 +71,7 @@ class Headers(MultiDict): For use with the "Set-Cookie" header, see :py:meth:`get_all`. """ - def __init__(self, fields=None, **headers): + def __init__(self, fields=(), **headers): """ Args: fields: (optional) list of ``(name, value)`` header byte tuples, @@ -91,7 +92,7 @@ class Headers(MultiDict): headers = { _always_bytes(name).replace(b"_", b"-"): _always_bytes(value) for name, value in six.iteritems(headers) - } + } self.update(headers) @staticmethod @@ -174,3 +175,30 @@ class Headers(MultiDict): fields.append([name, value]) self.fields = fields return replacements + + +def parse_content_type(c): + """ + A simple parser for content-type values. Returns a (type, subtype, + parameters) tuple, where type and subtype are strings, and parameters + is a dict. If the string could not be parsed, return None. + + E.g. the following string: + + text/html; charset=UTF-8 + + Returns: + + ("text", "html", {"charset": "UTF-8"}) + """ + parts = c.split(";", 1) + ts = parts[0].split("/", 1) + if len(ts) != 2: + return None + d = {} + if len(parts) == 2: + for i in parts[1].split(";"): + clause = i.split("=", 1) + if len(clause) == 2: + d[clause[0].strip()] = clause[1].strip() + return ts[0].lower(), ts[1].lower(), d diff --git a/netlib/http/http1/assemble.py b/netlib/http/http1/assemble.py index f06ad5a1..00d1563b 100644 --- a/netlib/http/http1/assemble.py +++ b/netlib/http/http1/assemble.py @@ -1,12 +1,12 @@ from __future__ import absolute_import, print_function, division -from ... import utils -import itertools -from ...exceptions import HttpException +from netlib import utils +from netlib import exceptions + def assemble_request(request): if request.content is None: - raise HttpException("Cannot assemble flow with missing content") + raise exceptions.HttpException("Cannot assemble flow with missing content") head = assemble_request_head(request) body = b"".join(assemble_body(request.data.headers, [request.data.content])) return head + body @@ -20,7 +20,7 @@ def assemble_request_head(request): def assemble_response(response): if response.content is None: - raise HttpException("Cannot assemble flow with missing content") + raise exceptions.HttpException("Cannot assemble flow with missing content") head = assemble_response_head(response) body = b"".join(assemble_body(response.data.headers, [response.data.content])) return head + body diff --git a/netlib/http/http1/read.py b/netlib/http/http1/read.py index d30976bd..bf4c2f0c 100644 --- a/netlib/http/http1/read.py +++ b/netlib/http/http1/read.py @@ -3,9 +3,24 @@ import time import sys import re -from ... import utils -from ...exceptions import HttpReadDisconnect, HttpSyntaxException, HttpException, TcpDisconnect -from .. import Request, Response, Headers +from netlib.http import request +from netlib.http import response +from netlib.http import headers +from netlib.http import url +from netlib import utils +from netlib import exceptions + + +def get_header_tokens(headers, key): + """ + Retrieve all tokens for a header key. A number of different headers + follow a pattern where each header line can containe comma-separated + tokens, and headers can be set multiple times. + """ + if key not in headers: + return [] + tokens = headers[key].split(",") + return [token.strip() for token in tokens] def read_request(rfile, body_size_limit=None): @@ -27,9 +42,9 @@ def read_request_head(rfile): The HTTP request object (without body) Raises: - HttpReadDisconnect: No bytes can be read from rfile. - HttpSyntaxException: The input is malformed HTTP. - HttpException: Any other error occured. + exceptions.HttpReadDisconnect: No bytes can be read from rfile. + exceptions.HttpSyntaxException: The input is malformed HTTP. + exceptions.HttpException: Any other error occured. """ timestamp_start = time.time() if hasattr(rfile, "reset_timestamps"): @@ -42,7 +57,7 @@ def read_request_head(rfile): # more accurate timestamp_start timestamp_start = rfile.first_byte_timestamp - return Request( + return request.Request( form, method, scheme, host, port, path, http_version, headers, None, timestamp_start ) @@ -66,9 +81,9 @@ def read_response_head(rfile): The HTTP request object (without body) Raises: - HttpReadDisconnect: No bytes can be read from rfile. - HttpSyntaxException: The input is malformed HTTP. - HttpException: Any other error occured. + exceptions.HttpReadDisconnect: No bytes can be read from rfile. + exceptions.HttpSyntaxException: The input is malformed HTTP. + exceptions.HttpException: Any other error occured. """ timestamp_start = time.time() @@ -82,7 +97,7 @@ def read_response_head(rfile): # more accurate timestamp_start timestamp_start = rfile.first_byte_timestamp - return Response(http_version, status_code, message, headers, None, timestamp_start) + return response.Response(http_version, status_code, message, headers, None, timestamp_start) def read_body(rfile, expected_size, limit=None, max_chunk_size=4096): @@ -99,7 +114,7 @@ def read_body(rfile, expected_size, limit=None, max_chunk_size=4096): A generator that yields byte chunks of the content. Raises: - HttpException, if an error occurs + exceptions.HttpException, if an error occurs Caveats: max_chunk_size is not considered if the transfer encoding is chunked. @@ -114,7 +129,7 @@ def read_body(rfile, expected_size, limit=None, max_chunk_size=4096): yield x elif expected_size >= 0: if limit is not None and expected_size > limit: - raise HttpException( + raise exceptions.HttpException( "HTTP Body too large. " "Limit is {}, content length was advertised as {}".format(limit, expected_size) ) @@ -123,7 +138,7 @@ def read_body(rfile, expected_size, limit=None, max_chunk_size=4096): chunk_size = min(bytes_left, max_chunk_size) content = rfile.read(chunk_size) if len(content) < chunk_size: - raise HttpException("Unexpected EOF") + raise exceptions.HttpException("Unexpected EOF") yield content bytes_left -= chunk_size else: @@ -137,7 +152,7 @@ def read_body(rfile, expected_size, limit=None, max_chunk_size=4096): bytes_left -= chunk_size not_done = rfile.read(1) if not_done: - raise HttpException("HTTP body too large. Limit is {}.".format(limit)) + raise exceptions.HttpException("HTTP body too large. Limit is {}.".format(limit)) def connection_close(http_version, headers): @@ -147,7 +162,7 @@ def connection_close(http_version, headers): """ # At first, check if we have an explicit Connection header. if "connection" in headers: - tokens = utils.get_header_tokens(headers, "connection") + tokens = get_header_tokens(headers, "connection") if "close" in tokens: return True elif "keep-alive" in tokens: @@ -167,7 +182,7 @@ def expected_http_body_size(request, response=None): - -1, if all data should be read until end of stream. Raises: - HttpSyntaxException, if the content length header is invalid + exceptions.HttpSyntaxException, if the content length header is invalid """ # Determine response size according to # http://tools.ietf.org/html/rfc7230#section-3.3 @@ -202,7 +217,7 @@ def expected_http_body_size(request, response=None): raise ValueError() return size except ValueError: - raise HttpSyntaxException("Unparseable Content Length") + raise exceptions.HttpSyntaxException("Unparseable Content Length") if is_request: return 0 return -1 @@ -214,19 +229,19 @@ def _get_first_line(rfile): if line == b"\r\n" or line == b"\n": # Possible leftover from previous message line = rfile.readline() - except TcpDisconnect: - raise HttpReadDisconnect("Remote disconnected") + except exceptions.TcpDisconnect: + raise exceptions.HttpReadDisconnect("Remote disconnected") if not line: - raise HttpReadDisconnect("Remote disconnected") + raise exceptions.HttpReadDisconnect("Remote disconnected") return line.strip() def _read_request_line(rfile): try: line = _get_first_line(rfile) - except HttpReadDisconnect: + except exceptions.HttpReadDisconnect: # We want to provide a better error message. - raise HttpReadDisconnect("Client disconnected") + raise exceptions.HttpReadDisconnect("Client disconnected") try: method, path, http_version = line.split(b" ") @@ -240,11 +255,11 @@ def _read_request_line(rfile): scheme, path = None, None else: form = "absolute" - scheme, host, port, path = utils.parse_url(path) + scheme, host, port, path = url.parse(path) _check_http_version(http_version) except ValueError: - raise HttpSyntaxException("Bad HTTP request line: {}".format(line)) + raise exceptions.HttpSyntaxException("Bad HTTP request line: {}".format(line)) return form, method, scheme, host, port, path, http_version @@ -263,7 +278,7 @@ def _parse_authority_form(hostport): if not utils.is_valid_host(host) or not utils.is_valid_port(port): raise ValueError() except ValueError: - raise HttpSyntaxException("Invalid host specification: {}".format(hostport)) + raise exceptions.HttpSyntaxException("Invalid host specification: {}".format(hostport)) return host, port @@ -271,9 +286,9 @@ def _parse_authority_form(hostport): def _read_response_line(rfile): try: line = _get_first_line(rfile) - except HttpReadDisconnect: + except exceptions.HttpReadDisconnect: # We want to provide a better error message. - raise HttpReadDisconnect("Server disconnected") + raise exceptions.HttpReadDisconnect("Server disconnected") try: @@ -286,14 +301,14 @@ def _read_response_line(rfile): _check_http_version(http_version) except ValueError: - raise HttpSyntaxException("Bad HTTP response line: {}".format(line)) + raise exceptions.HttpSyntaxException("Bad HTTP response line: {}".format(line)) return http_version, status_code, message def _check_http_version(http_version): if not re.match(br"^HTTP/\d\.\d$", http_version): - raise HttpSyntaxException("Unknown HTTP version: {}".format(http_version)) + raise exceptions.HttpSyntaxException("Unknown HTTP version: {}".format(http_version)) def _read_headers(rfile): @@ -305,7 +320,7 @@ def _read_headers(rfile): A headers object Raises: - HttpSyntaxException + exceptions.HttpSyntaxException """ ret = [] while True: @@ -314,7 +329,7 @@ def _read_headers(rfile): break if line[0] in b" \t": if not ret: - raise HttpSyntaxException("Invalid headers") + raise exceptions.HttpSyntaxException("Invalid headers") # continued header ret[-1] = (ret[-1][0], ret[-1][1] + b'\r\n ' + line.strip()) else: @@ -325,8 +340,8 @@ def _read_headers(rfile): raise ValueError() ret.append((name, value)) except ValueError: - raise HttpSyntaxException("Invalid headers") - return Headers(ret) + raise exceptions.HttpSyntaxException("Invalid headers") + return headers.Headers(ret) def _read_chunked(rfile, limit=sys.maxsize): @@ -341,22 +356,22 @@ def _read_chunked(rfile, limit=sys.maxsize): while True: line = rfile.readline(128) if line == b"": - raise HttpException("Connection closed prematurely") + raise exceptions.HttpException("Connection closed prematurely") if line != b"\r\n" and line != b"\n": try: length = int(line, 16) except ValueError: - raise HttpSyntaxException("Invalid chunked encoding length: {}".format(line)) + raise exceptions.HttpSyntaxException("Invalid chunked encoding length: {}".format(line)) total += length if total > limit: - raise HttpException( + raise exceptions.HttpException( "HTTP Body too large. Limit is {}, " "chunked content longer than {}".format(limit, total) ) chunk = rfile.read(length) suffix = rfile.readline(5) if suffix != b"\r\n": - raise HttpSyntaxException("Malformed chunked body") + raise exceptions.HttpSyntaxException("Malformed chunked body") if length == 0: return yield chunk diff --git a/netlib/http/http2/__init__.py b/netlib/http/http2/__init__.py index 7043d36f..633e6a20 100644 --- a/netlib/http/http2/__init__.py +++ b/netlib/http/http2/__init__.py @@ -1,6 +1,8 @@ from __future__ import absolute_import, print_function, division from .connections import HTTP2Protocol +from netlib.http.http2 import framereader __all__ = [ - "HTTP2Protocol" + "HTTP2Protocol", + "framereader", ] diff --git a/netlib/http/http2/connections.py b/netlib/http/http2/connections.py index 6643b6b9..8667d370 100644 --- a/netlib/http/http2/connections.py +++ b/netlib/http/http2/connections.py @@ -2,11 +2,15 @@ from __future__ import (absolute_import, print_function, division) import itertools import time -from hpack.hpack import Encoder, Decoder -from ... import utils -from .. import Headers, Response, Request +import hyperframe.frame -from hyperframe import frame +from hpack.hpack import Encoder, Decoder +from netlib import utils +from netlib.http import url +import netlib.http.headers +import netlib.http.response +import netlib.http.request +from netlib.http.http2 import framereader class TCPHandler(object): @@ -38,12 +42,12 @@ class HTTP2Protocol(object): CLIENT_CONNECTION_PREFACE = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' HTTP2_DEFAULT_SETTINGS = { - frame.SettingsFrame.HEADER_TABLE_SIZE: 4096, - frame.SettingsFrame.ENABLE_PUSH: 1, - frame.SettingsFrame.MAX_CONCURRENT_STREAMS: None, - frame.SettingsFrame.INITIAL_WINDOW_SIZE: 2 ** 16 - 1, - frame.SettingsFrame.MAX_FRAME_SIZE: 2 ** 14, - frame.SettingsFrame.MAX_HEADER_LIST_SIZE: None, + hyperframe.frame.SettingsFrame.HEADER_TABLE_SIZE: 4096, + hyperframe.frame.SettingsFrame.ENABLE_PUSH: 1, + hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: None, + hyperframe.frame.SettingsFrame.INITIAL_WINDOW_SIZE: 2 ** 16 - 1, + hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE: 2 ** 14, + hyperframe.frame.SettingsFrame.MAX_HEADER_LIST_SIZE: None, } def __init__( @@ -98,6 +102,11 @@ class HTTP2Protocol(object): method = headers.get(':method', 'GET') scheme = headers.get(':scheme', 'https') path = headers.get(':path', '/') + + headers.clear(":method") + headers.clear(":scheme") + headers.clear(":path") + host = None port = None @@ -112,7 +121,7 @@ class HTTP2Protocol(object): else: first_line_format = "absolute" # FIXME: verify if path or :host contains what we need - scheme, host, port, _ = utils.parse_url(path) + scheme, host, port, _ = url.parse(path) scheme = scheme.decode('ascii') host = host.decode('ascii') @@ -122,7 +131,7 @@ class HTTP2Protocol(object): port = 80 if scheme == 'http' else 443 port = int(port) - request = Request( + request = netlib.http.request.Request( first_line_format, method.encode('ascii'), scheme.encode('ascii'), @@ -170,7 +179,7 @@ class HTTP2Protocol(object): else: timestamp_end = None - response = Response( + response = netlib.http.response.Response( b"HTTP/2.0", int(headers.get(':status', 502)), b'', @@ -184,15 +193,15 @@ class HTTP2Protocol(object): return response def assemble(self, message): - if isinstance(message, Request): + if isinstance(message, netlib.http.request.Request): return self.assemble_request(message) - elif isinstance(message, Response): + elif isinstance(message, netlib.http.response.Response): return self.assemble_response(message) else: raise ValueError("HTTP message not supported.") def assemble_request(self, request): - assert isinstance(request, Request) + assert isinstance(request, netlib.http.request.Request) authority = self.tcp_handler.sni if self.tcp_handler.sni else self.tcp_handler.address.host if self.tcp_handler.address.port != 443: @@ -202,12 +211,9 @@ class HTTP2Protocol(object): if ':authority' not in headers: headers.insert(0, b':authority', authority.encode('ascii')) - if ':scheme' not in headers: - headers.insert(0, b':scheme', request.scheme.encode('ascii')) - if ':path' not in headers: - headers.insert(0, b':path', request.path.encode('ascii')) - if ':method' not in headers: - headers.insert(0, b':method', request.method.encode('ascii')) + headers.insert(0, b':scheme', request.scheme.encode('ascii')) + headers.insert(0, b':path', request.path.encode('ascii')) + headers.insert(0, b':method', request.method.encode('ascii')) if hasattr(request, 'stream_id'): stream_id = request.stream_id @@ -219,7 +225,7 @@ class HTTP2Protocol(object): self._create_body(request.body, stream_id))) def assemble_response(self, response): - assert isinstance(response, Response) + assert isinstance(response, netlib.http.response.Response) headers = response.headers.copy() @@ -251,9 +257,9 @@ class HTTP2Protocol(object): magic = self.tcp_handler.rfile.safe_read(magic_length) assert magic == self.CLIENT_CONNECTION_PREFACE - frm = frame.SettingsFrame(settings={ - frame.SettingsFrame.ENABLE_PUSH: 0, - frame.SettingsFrame.MAX_CONCURRENT_STREAMS: 1, + frm = hyperframe.frame.SettingsFrame(settings={ + hyperframe.frame.SettingsFrame.ENABLE_PUSH: 0, + hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: 1, }) self.send_frame(frm, hide=True) self._receive_settings(hide=True) @@ -264,7 +270,7 @@ class HTTP2Protocol(object): self.tcp_handler.wfile.write(self.CLIENT_CONNECTION_PREFACE) - self.send_frame(frame.SettingsFrame(), hide=True) + self.send_frame(hyperframe.frame.SettingsFrame(), hide=True) self._receive_settings(hide=True) # server announces own settings self._receive_settings(hide=True) # server acks my settings @@ -277,18 +283,18 @@ class HTTP2Protocol(object): def read_frame(self, hide=False): while True: - frm = utils.http2_read_frame(self.tcp_handler.rfile) + frm = framereader.http2_read_frame(self.tcp_handler.rfile) if not hide and self.dump_frames: # pragma no cover print(frm.human_readable("<<")) - if isinstance(frm, frame.PingFrame): - raw_bytes = frame.PingFrame(flags=['ACK'], payload=frm.payload).serialize() + if isinstance(frm, hyperframe.frame.PingFrame): + raw_bytes = hyperframe.frame.PingFrame(flags=['ACK'], payload=frm.payload).serialize() self.tcp_handler.wfile.write(raw_bytes) self.tcp_handler.wfile.flush() continue - if isinstance(frm, frame.SettingsFrame) and 'ACK' not in frm.flags: + if isinstance(frm, hyperframe.frame.SettingsFrame) and 'ACK' not in frm.flags: self._apply_settings(frm.settings, hide) - if isinstance(frm, frame.DataFrame) and frm.flow_controlled_length > 0: + if isinstance(frm, hyperframe.frame.DataFrame) and frm.flow_controlled_length > 0: self._update_flow_control_window(frm.stream_id, frm.flow_controlled_length) return frm @@ -300,7 +306,7 @@ class HTTP2Protocol(object): return True def _handle_unexpected_frame(self, frm): - if isinstance(frm, frame.SettingsFrame): + if isinstance(frm, hyperframe.frame.SettingsFrame): return if self.unhandled_frame_cb: self.unhandled_frame_cb(frm) @@ -308,7 +314,7 @@ class HTTP2Protocol(object): def _receive_settings(self, hide=False): while True: frm = self.read_frame(hide) - if isinstance(frm, frame.SettingsFrame): + if isinstance(frm, hyperframe.frame.SettingsFrame): break else: self._handle_unexpected_frame(frm) @@ -332,31 +338,31 @@ class HTTP2Protocol(object): old_value = '-' self.http2_settings[setting] = value - frm = frame.SettingsFrame(flags=['ACK']) + frm = hyperframe.frame.SettingsFrame(flags=['ACK']) self.send_frame(frm, hide) def _update_flow_control_window(self, stream_id, increment): - frm = frame.WindowUpdateFrame(stream_id=0, window_increment=increment) + frm = hyperframe.frame.WindowUpdateFrame(stream_id=0, window_increment=increment) self.send_frame(frm) - frm = frame.WindowUpdateFrame(stream_id=stream_id, window_increment=increment) + frm = hyperframe.frame.WindowUpdateFrame(stream_id=stream_id, window_increment=increment) self.send_frame(frm) def _create_headers(self, headers, stream_id, end_stream=True): def frame_cls(chunks): for i in chunks: if i == 0: - yield frame.HeadersFrame, i + yield hyperframe.frame.HeadersFrame, i else: - yield frame.ContinuationFrame, i + yield hyperframe.frame.ContinuationFrame, i header_block_fragment = self.encoder.encode(headers.fields) - chunk_size = self.http2_settings[frame.SettingsFrame.MAX_FRAME_SIZE] + chunk_size = self.http2_settings[hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE] chunks = range(0, len(header_block_fragment), chunk_size) frms = [frm_cls( flags=[], stream_id=stream_id, - data=header_block_fragment[i:i+chunk_size]) for frm_cls, i in frame_cls(chunks)] + data=header_block_fragment[i:i + chunk_size]) for frm_cls, i in frame_cls(chunks)] frms[-1].flags.add('END_HEADERS') if end_stream: @@ -372,12 +378,12 @@ class HTTP2Protocol(object): if body is None or len(body) == 0: return b'' - chunk_size = self.http2_settings[frame.SettingsFrame.MAX_FRAME_SIZE] + chunk_size = self.http2_settings[hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE] chunks = range(0, len(body), chunk_size) - frms = [frame.DataFrame( + frms = [hyperframe.frame.DataFrame( flags=[], stream_id=stream_id, - data=body[i:i+chunk_size]) for i in chunks] + data=body[i:i + chunk_size]) for i in chunks] frms[-1].flags.add('END_STREAM') if self.dump_frames: # pragma no cover @@ -398,7 +404,7 @@ class HTTP2Protocol(object): while True: frm = self.read_frame() if ( - (isinstance(frm, frame.HeadersFrame) or isinstance(frm, frame.ContinuationFrame)) and + (isinstance(frm, hyperframe.frame.HeadersFrame) or isinstance(frm, hyperframe.frame.ContinuationFrame)) and (stream_id is None or frm.stream_id == stream_id) ): stream_id = frm.stream_id @@ -412,14 +418,14 @@ class HTTP2Protocol(object): while body_expected: frm = self.read_frame() - if isinstance(frm, frame.DataFrame) and frm.stream_id == stream_id: + if isinstance(frm, hyperframe.frame.DataFrame) and frm.stream_id == stream_id: body += frm.data if 'END_STREAM' in frm.flags: break else: self._handle_unexpected_frame(frm) - headers = Headers( + headers = netlib.http.headers.Headers( (k.encode('ascii'), v.encode('ascii')) for k, v in self.decoder.decode(header_blocks) ) diff --git a/netlib/http/http2/framereader.py b/netlib/http/http2/framereader.py new file mode 100644 index 00000000..d45be646 --- /dev/null +++ b/netlib/http/http2/framereader.py @@ -0,0 +1,21 @@ +import codecs + +import hyperframe + + +def http2_read_raw_frame(rfile): + header = rfile.safe_read(9) + length = int(codecs.encode(header[:3], 'hex_codec'), 16) + + if length == 4740180: + raise ValueError("Length field looks more like HTTP/1.1: %s" % rfile.peek(20)) + + body = rfile.safe_read(length) + return [header, body] + + +def http2_read_frame(rfile): + header, body = http2_read_raw_frame(rfile) + frame, length = hyperframe.frame.Frame.parse_frame_header(header) + frame.parse_body(memoryview(body)) + return frame diff --git a/netlib/http/message.py b/netlib/http/message.py index 028f43a1..b633b671 100644 --- a/netlib/http/message.py +++ b/netlib/http/message.py @@ -4,20 +4,25 @@ import warnings import six -from ..multidict import MultiDict -from .headers import Headers -from .. import encoding, utils +from netlib import encoding, strutils, basetypes +from netlib.http import headers if six.PY2: # pragma: no cover - _native = lambda x: x - _always_bytes = lambda x: x + def _native(x): + return x + + def _always_bytes(x): + return x else: - # While the HTTP head _should_ be ASCII, it's not uncommon for certain headers to be utf-8 encoded. - _native = lambda x: x.decode("utf-8", "surrogateescape") - _always_bytes = lambda x: utils.always_bytes(x, "utf-8", "surrogateescape") + # While headers _should_ be ASCII, it's not uncommon for certain headers to be utf-8 encoded. + def _native(x): + return x.decode("utf-8", "surrogateescape") + + def _always_bytes(x): + return strutils.always_bytes(x, "utf-8", "surrogateescape") -class MessageData(utils.Serializable): +class MessageData(basetypes.Serializable): def __eq__(self, other): if isinstance(other, MessageData): return self.__dict__ == other.__dict__ @@ -32,7 +37,7 @@ class MessageData(utils.Serializable): def set_state(self, state): for k, v in state.items(): if k == "headers": - v = Headers.from_state(v) + v = headers.Headers.from_state(v) setattr(self, k, v) def get_state(self): @@ -42,11 +47,11 @@ class MessageData(utils.Serializable): @classmethod def from_state(cls, state): - state["headers"] = Headers.from_state(state["headers"]) + state["headers"] = headers.Headers.from_state(state["headers"]) return cls(**state) -class Message(utils.Serializable): +class Message(basetypes.Serializable): def __eq__(self, other): if isinstance(other, Message): return self.data == other.data @@ -66,7 +71,7 @@ class Message(utils.Serializable): @classmethod def from_state(cls, state): - state["headers"] = Headers.from_state(state["headers"]) + state["headers"] = headers.Headers.from_state(state["headers"]) return cls(**state) @property @@ -195,7 +200,7 @@ class Message(utils.Serializable): replacements = 0 if self.content: with decoded(self): - self.content, replacements = utils.safe_subn( + self.content, replacements = strutils.safe_subn( pattern, repl, self.content, flags=flags ) replacements += self.headers.replace(pattern, repl, flags) diff --git a/netlib/http/multipart.py b/netlib/http/multipart.py new file mode 100644 index 00000000..536b2809 --- /dev/null +++ b/netlib/http/multipart.py @@ -0,0 +1,32 @@ +import re + +from netlib.http import headers + + +def decode(hdrs, content): + """ + Takes a multipart boundary encoded string and returns list of (key, value) tuples. + """ + v = hdrs.get("content-type") + if v: + v = headers.parse_content_type(v) + if not v: + return [] + try: + boundary = v[2]["boundary"].encode("ascii") + except (KeyError, UnicodeError): + return [] + + rx = re.compile(br'\bname="([^"]+)"') + r = [] + + for i in content.split(b"--" + boundary): + parts = i.splitlines() + if len(parts) > 1 and parts[0][0:2] != b"--": + match = rx.search(parts[1]) + if match: + key = match.group(1) + value = b"".join(parts[3 + parts[2:].index(b""):]) + r.append((key, value)) + return r + return [] diff --git a/netlib/http/request.py b/netlib/http/request.py index 056a2d93..91d5f020 100644 --- a/netlib/http/request.py +++ b/netlib/http/request.py @@ -1,18 +1,18 @@ from __future__ import absolute_import, print_function, division import re -import warnings import six from six.moves import urllib -from netlib import utils +from netlib import encoding +from netlib import multidict +from netlib import strutils +from netlib.http import multipart from netlib.http import cookies -from netlib.odict import ODict -from .. import encoding -from ..multidict import MultiDictView -from .headers import Headers -from .message import Message, _native, _always_bytes, MessageData +from netlib.http import headers as nheaders +from netlib.http import message +import netlib.http.url # This regex extracts & splits the host header into host and port. # Handles the edge case of IPv6 addresses containing colons. @@ -20,11 +20,11 @@ from .message import Message, _native, _always_bytes, MessageData host_header_re = re.compile(r"^(?P<host>[^:]+|\[.+\])(?::(?P<port>\d+))?$") -class RequestData(MessageData): - def __init__(self, first_line_format, method, scheme, host, port, path, http_version, headers=None, content=None, +class RequestData(message.MessageData): + def __init__(self, first_line_format, method, scheme, host, port, path, http_version, headers=(), content=None, timestamp_start=None, timestamp_end=None): - if not isinstance(headers, Headers): - headers = Headers(headers) + if not isinstance(headers, nheaders.Headers): + headers = nheaders.Headers(headers) self.first_line_format = first_line_format self.method = method @@ -39,7 +39,7 @@ class RequestData(MessageData): self.timestamp_end = timestamp_end -class Request(Message): +class Request(message.Message): """ An HTTP request. """ @@ -67,7 +67,7 @@ class Request(Message): """ # TODO: Proper distinction between text and bytes. c = super(Request, self).replace(pattern, repl, flags) - self.path, pc = utils.safe_subn( + self.path, pc = strutils.safe_subn( pattern, repl, self.path, flags=flags ) c += pc @@ -91,22 +91,22 @@ class Request(Message): """ HTTP request method, e.g. "GET". """ - return _native(self.data.method).upper() + return message._native(self.data.method).upper() @method.setter def method(self, method): - self.data.method = _always_bytes(method) + self.data.method = message._always_bytes(method) @property def scheme(self): """ HTTP request scheme, which should be "http" or "https". """ - return _native(self.data.scheme) + return message._native(self.data.scheme) @scheme.setter def scheme(self, scheme): - self.data.scheme = _always_bytes(scheme) + self.data.scheme = message._always_bytes(scheme) @property def host(self): @@ -168,11 +168,11 @@ class Request(Message): if self.data.path is None: return None else: - return _native(self.data.path) + return message._native(self.data.path) @path.setter def path(self, path): - self.data.path = _always_bytes(path) + self.data.path = message._always_bytes(path) @property def url(self): @@ -181,11 +181,11 @@ class Request(Message): """ if self.first_line_format == "authority": return "%s:%d" % (self.host, self.port) - return utils.unparse_url(self.scheme, self.host, self.port, self.path) + return netlib.http.url.unparse(self.scheme, self.host, self.port, self.path) @url.setter def url(self, url): - self.scheme, self.host, self.port, self.path = utils.parse_url(url) + self.scheme, self.host, self.port, self.path = netlib.http.url.parse(url) def _parse_host_header(self): """Extract the host and port from Host header""" @@ -221,28 +221,28 @@ class Request(Message): """ if self.first_line_format == "authority": return "%s:%d" % (self.pretty_host, self.port) - return utils.unparse_url(self.scheme, self.pretty_host, self.port, self.path) + return netlib.http.url.unparse(self.scheme, self.pretty_host, self.port, self.path) @property def query(self): - # type: () -> MultiDictView + # type: () -> multidict.MultiDictView """ The request query string as an :py:class:`MultiDictView` object. """ - return MultiDictView( + return multidict.MultiDictView( self._get_query, self._set_query ) def _get_query(self): _, _, _, _, query, _ = urllib.parse.urlparse(self.url) - return tuple(utils.urldecode(query)) + return tuple(netlib.http.url.decode(query)) def _set_query(self, value): - query = utils.urlencode(value) + query = netlib.http.url.encode(value) scheme, netloc, path, params, _, fragment = urllib.parse.urlparse(self.url) - _, _, _, self.path = utils.parse_url( - urllib.parse.urlunparse([scheme, netloc, path, params, query, fragment])) + _, _, _, self.path = netlib.http.url.parse( + urllib.parse.urlunparse([scheme, netloc, path, params, query, fragment])) @query.setter def query(self, value): @@ -250,13 +250,13 @@ class Request(Message): @property def cookies(self): - # type: () -> MultiDictView + # type: () -> multidict.MultiDictView """ The request cookies. - An empty :py:class:`MultiDictView` object if the cookie monster ate them all. + An empty :py:class:`multidict.MultiDictView` object if the cookie monster ate them all. """ - return MultiDictView( + return multidict.MultiDictView( self._get_cookies, self._set_cookies ) @@ -289,8 +289,8 @@ class Request(Message): components = map(lambda x: urllib.parse.quote(x, safe=""), components) path = "/" + "/".join(components) scheme, netloc, _, params, query, fragment = urllib.parse.urlparse(self.url) - _, _, _, self.path = utils.parse_url( - urllib.parse.urlunparse([scheme, netloc, path, params, query, fragment])) + _, _, _, self.path = netlib.http.url.parse( + urllib.parse.urlunparse([scheme, netloc, path, params, query, fragment])) def anticache(self): """ @@ -329,11 +329,11 @@ class Request(Message): @property def urlencoded_form(self): """ - The URL-encoded form data as an :py:class:`MultiDictView` object. - An empty MultiDictView if the content-type indicates non-form data + The URL-encoded form data as an :py:class:`multidict.MultiDictView` object. + An empty multidict.MultiDictView if the content-type indicates non-form data or the content could not be parsed. """ - return MultiDictView( + return multidict.MultiDictView( self._get_urlencoded_form, self._set_urlencoded_form ) @@ -341,7 +341,7 @@ class Request(Message): def _get_urlencoded_form(self): is_valid_content_type = "application/x-www-form-urlencoded" in self.headers.get("content-type", "").lower() if is_valid_content_type: - return tuple(utils.urldecode(self.content)) + return tuple(netlib.http.url.decode(self.content)) return () def _set_urlencoded_form(self, value): @@ -350,7 +350,7 @@ class Request(Message): This will overwrite the existing content if there is one. """ self.headers["content-type"] = "application/x-www-form-urlencoded" - self.content = utils.urlencode(value) + self.content = netlib.http.url.encode(value) @urlencoded_form.setter def urlencoded_form(self, value): @@ -362,7 +362,7 @@ class Request(Message): The multipart form data as an :py:class:`MultipartFormDict` object. None if the content-type indicates non-form data. """ - return MultiDictView( + return multidict.MultiDictView( self._get_multipart_form, self._set_multipart_form ) @@ -370,7 +370,7 @@ class Request(Message): def _get_multipart_form(self): is_valid_content_type = "multipart/form-data" in self.headers.get("content-type", "").lower() if is_valid_content_type: - return utils.multipartdecode(self.headers, self.content) + return multipart.decode(self.headers, self.content) return () def _set_multipart_form(self, value): diff --git a/netlib/http/response.py b/netlib/http/response.py index 7d272e10..44b58be6 100644 --- a/netlib/http/response.py +++ b/netlib/http/response.py @@ -3,18 +3,18 @@ from __future__ import absolute_import, print_function, division from email.utils import parsedate_tz, formatdate, mktime_tz import time -from . import cookies -from .headers import Headers -from .message import Message, _native, _always_bytes, MessageData -from ..multidict import MultiDictView -from .. import utils +from netlib.http import cookies +from netlib.http import headers as nheaders +from netlib.http import message +from netlib import multidict +from netlib import human -class ResponseData(MessageData): - def __init__(self, http_version, status_code, reason=None, headers=None, content=None, +class ResponseData(message.MessageData): + def __init__(self, http_version, status_code, reason=None, headers=(), content=None, timestamp_start=None, timestamp_end=None): - if not isinstance(headers, Headers): - headers = Headers(headers) + if not isinstance(headers, nheaders.Headers): + headers = nheaders.Headers(headers) self.http_version = http_version self.status_code = status_code @@ -25,7 +25,7 @@ class ResponseData(MessageData): self.timestamp_end = timestamp_end -class Response(Message): +class Response(message.Message): """ An HTTP response. """ @@ -36,7 +36,7 @@ class Response(Message): if self.content: details = "{}, {}".format( self.headers.get("content-type", "unknown content type"), - utils.pretty_size(len(self.content)) + human.pretty_size(len(self.content)) ) else: details = "no content" @@ -63,17 +63,17 @@ class Response(Message): HTTP Reason Phrase, e.g. "Not Found". This is always :py:obj:`None` for HTTP2 requests, because HTTP2 responses do not contain a reason phrase. """ - return _native(self.data.reason) + return message._native(self.data.reason) @reason.setter def reason(self, reason): - self.data.reason = _always_bytes(reason) + self.data.reason = message._always_bytes(reason) @property def cookies(self): - # type: () -> MultiDictView + # type: () -> multidict.MultiDictView """ - The response cookies. A possibly empty :py:class:`MultiDictView`, where the keys are + The response cookies. A possibly empty :py:class:`multidict.MultiDictView`, where the keys are cookie name strings, and values are (value, attr) tuples. Value is a string, and attr is an ODictCaseless containing cookie attributes. Within attrs, unary attributes (e.g. HTTPOnly) are indicated by a Null value. @@ -81,7 +81,7 @@ class Response(Message): Caveats: Updating the attr """ - return MultiDictView( + return multidict.MultiDictView( self._get_cookies, self._set_cookies ) diff --git a/netlib/http/url.py b/netlib/http/url.py new file mode 100644 index 00000000..5d461387 --- /dev/null +++ b/netlib/http/url.py @@ -0,0 +1,96 @@ +import six +from six.moves import urllib + +from netlib import utils + + +# PY2 workaround +def decode_parse_result(result, enc): + if hasattr(result, "decode"): + return result.decode(enc) + else: + return urllib.parse.ParseResult(*[x.decode(enc) for x in result]) + + +# PY2 workaround +def encode_parse_result(result, enc): + if hasattr(result, "encode"): + return result.encode(enc) + else: + return urllib.parse.ParseResult(*[x.encode(enc) for x in result]) + + +def parse(url): + """ + URL-parsing function that checks that + - port is an integer 0-65535 + - host is a valid IDNA-encoded hostname with no null-bytes + - path is valid ASCII + + Args: + A URL (as bytes or as unicode) + + Returns: + A (scheme, host, port, path) tuple + + Raises: + ValueError, if the URL is not properly formatted. + """ + parsed = urllib.parse.urlparse(url) + + if not parsed.hostname: + raise ValueError("No hostname given") + + if isinstance(url, six.binary_type): + host = parsed.hostname + + # this should not raise a ValueError, + # but we try to be very forgiving here and accept just everything. + # decode_parse_result(parsed, "ascii") + else: + host = parsed.hostname.encode("idna") + parsed = encode_parse_result(parsed, "ascii") + + port = parsed.port + if not port: + port = 443 if parsed.scheme == b"https" else 80 + + full_path = urllib.parse.urlunparse( + (b"", b"", parsed.path, parsed.params, parsed.query, parsed.fragment) + ) + if not full_path.startswith(b"/"): + full_path = b"/" + full_path + + if not utils.is_valid_host(host): + raise ValueError("Invalid Host") + if not utils.is_valid_port(port): + raise ValueError("Invalid Port") + + return parsed.scheme, host, port, full_path + + +def unparse(scheme, host, port, path=""): + """ + Returns a URL string, constructed from the specified components. + + Args: + All args must be str. + """ + if path == "*": + path = "" + return "%s://%s%s" % (scheme, utils.hostport(scheme, host, port), path) + + +def encode(s): + """ + Takes a list of (key, value) tuples and returns a urlencoded string. + """ + s = [tuple(i) for i in s] + return urllib.parse.urlencode(s, False) + + +def decode(s): + """ + Takes a urlencoded string and returns a list of (key, value) tuples. + """ + return urllib.parse.parse_qsl(s, keep_blank_values=True) diff --git a/netlib/human.py b/netlib/human.py new file mode 100644 index 00000000..a007adc7 --- /dev/null +++ b/netlib/human.py @@ -0,0 +1,50 @@ + +SIZE_TABLE = [ + ("b", 1024 ** 0), + ("k", 1024 ** 1), + ("m", 1024 ** 2), + ("g", 1024 ** 3), + ("t", 1024 ** 4), +] + +SIZE_UNITS = dict(SIZE_TABLE) + + +def pretty_size(size): + for bottom, top in zip(SIZE_TABLE, SIZE_TABLE[1:]): + if bottom[1] <= size < top[1]: + suf = bottom[0] + lim = bottom[1] + x = round(size / lim, 2) + if x == int(x): + x = int(x) + return str(x) + suf + return "%s%s" % (size, SIZE_TABLE[0][0]) + + +def parse_size(s): + try: + return int(s) + except ValueError: + pass + for i in SIZE_UNITS.keys(): + if s.endswith(i): + try: + return int(s[:-1]) * SIZE_UNITS[i] + except ValueError: + break + raise ValueError("Invalid size specification.") + + +def pretty_duration(secs): + formatters = [ + (100, "{:.0f}s"), + (10, "{:2.1f}s"), + (1, "{:1.2f}s"), + ] + + for limit, formatter in formatters: + if secs >= limit: + return formatter.format(secs) + # less than 1 sec + return "{:.0f}ms".format(secs * 1000) diff --git a/netlib/multidict.py b/netlib/multidict.py index 248acdec..dc0f3466 100644 --- a/netlib/multidict.py +++ b/netlib/multidict.py @@ -2,7 +2,6 @@ from __future__ import absolute_import, print_function, division from abc import ABCMeta, abstractmethod -from typing import Tuple, TypeVar try: from collections.abc import MutableMapping @@ -10,14 +9,13 @@ except ImportError: # pragma: no cover from collections import MutableMapping # Workaround for Python < 3.3 import six - -from .utils import Serializable +from netlib import basetypes @six.add_metaclass(ABCMeta) -class _MultiDict(MutableMapping, Serializable): +class _MultiDict(MutableMapping, basetypes.Serializable): def __repr__(self): - fields = tuple( + fields = ( repr(field) for field in self.fields ) @@ -172,6 +170,30 @@ class _MultiDict(MutableMapping, Serializable): else: return super(_MultiDict, self).items() + def clear(self, key): + """ + Removes all items with the specified key, and does not raise an + exception if the key does not exist. + """ + if key in self: + del self[key] + + def collect(self): + """ + Returns a list of (key, value) tuples, where values are either + singular if threre is only one matching item for a key, or a list + if there are more than one. The order of the keys matches the order + in the underlying fields list. + """ + coll = [] + for key in self: + values = self.get_all(key) + if len(values) == 1: + coll.append([key, values[0]]) + else: + coll.append([key, values]) + return coll + def to_dict(self): """ Get the MultiDict as a plain Python dict. @@ -191,12 +213,8 @@ class _MultiDict(MutableMapping, Serializable): } """ d = {} - for key in self: - values = self.get_all(key) - if len(values) == 1: - d[key] = values[0] - else: - d[key] = values + for k, v in self.collect(): + d[k] = v return d def get_state(self): @@ -207,13 +225,15 @@ class _MultiDict(MutableMapping, Serializable): @classmethod def from_state(cls, state): - return cls(tuple(x) for x in state) + return cls(state) class MultiDict(_MultiDict): - def __init__(self, fields=None): + def __init__(self, fields=()): super(MultiDict, self).__init__() - self.fields = tuple(fields) if fields else tuple() # type: Tuple[Tuple[bytes, bytes], ...] + self.fields = tuple( + tuple(i) for i in fields + ) @six.add_metaclass(ABCMeta) diff --git a/netlib/odict.py b/netlib/odict.py index 8a638dab..f9f55991 100644 --- a/netlib/odict.py +++ b/netlib/odict.py @@ -3,10 +3,10 @@ import copy import six -from .utils import Serializable, safe_subn +from netlib import basetypes, strutils -class ODict(Serializable): +class ODict(basetypes.Serializable): """ A dictionary-like object for managing ordered (key, value) data. Think @@ -139,9 +139,9 @@ class ODict(Serializable): """ new, count = [], 0 for k, v in self.lst: - k, c = safe_subn(pattern, repl, k, *args, **kwargs) + k, c = strutils.safe_subn(pattern, repl, k, *args, **kwargs) count += c - v, c = safe_subn(pattern, repl, v, *args, **kwargs) + v, c = strutils.safe_subn(pattern, repl, v, *args, **kwargs) count += c new.append([k, v]) self.lst = new diff --git a/netlib/socks.py b/netlib/socks.py index 57ccd1be..8d7c5279 100644 --- a/netlib/socks.py +++ b/netlib/socks.py @@ -2,7 +2,8 @@ from __future__ import (absolute_import, print_function, division) import struct import array import ipaddress -from . import tcp, utils + +from netlib import tcp, utils class SocksError(Exception): @@ -147,7 +148,7 @@ class UsernamePasswordAuth(object): class UsernamePasswordAuthResponse(object): - __slots__ = ("ver", "status") + __slots__ = ("ver", "status") def __init__(self, ver, status): self.ver = ver diff --git a/netlib/strutils.py b/netlib/strutils.py new file mode 100644 index 00000000..03b371f5 --- /dev/null +++ b/netlib/strutils.py @@ -0,0 +1,154 @@ +import re +import unicodedata +import codecs + +import six + + +def always_bytes(unicode_or_bytes, *encode_args): + if isinstance(unicode_or_bytes, six.text_type): + return unicode_or_bytes.encode(*encode_args) + return unicode_or_bytes + + +def native(s, *encoding_opts): + """ + Convert :py:class:`bytes` or :py:class:`unicode` to the native + :py:class:`str` type, using latin1 encoding if conversion is necessary. + + https://www.python.org/dev/peps/pep-3333/#a-note-on-string-types + """ + if not isinstance(s, (six.binary_type, six.text_type)): + raise TypeError("%r is neither bytes nor unicode" % s) + if six.PY3: + if isinstance(s, six.binary_type): + return s.decode(*encoding_opts) + else: + if isinstance(s, six.text_type): + return s.encode(*encoding_opts) + return s + + +def clean_bin(s, keep_spacing=True): + """ + Cleans binary data to make it safe to display. + + Args: + keep_spacing: If False, tabs and newlines will also be replaced. + """ + if isinstance(s, six.text_type): + if keep_spacing: + keep = u" \n\r\t" + else: + keep = u" " + return u"".join( + ch if (unicodedata.category(ch)[0] not in "CZ" or ch in keep) else u"." + for ch in s + ) + else: + if keep_spacing: + keep = (9, 10, 13) # \t, \n, \r, + else: + keep = () + return b"".join( + six.int2byte(ch) if (31 < ch < 127 or ch in keep) else b"." + for ch in six.iterbytes(s) + ) + + +def safe_subn(pattern, repl, target, *args, **kwargs): + """ + There are Unicode conversion problems with re.subn. We try to smooth + that over by casting the pattern and replacement to strings. We really + need a better solution that is aware of the actual content ecoding. + """ + return re.subn(str(pattern), str(repl), target, *args, **kwargs) + + +def bytes_to_escaped_str(data): + """ + Take bytes and return a safe string that can be displayed to the user. + + Single quotes are always escaped, double quotes are never escaped: + "'" + bytes_to_escaped_str(...) + "'" + gives a valid Python string. + """ + # TODO: We may want to support multi-byte characters without escaping them. + # One way to do would be calling .decode("utf8", "backslashreplace") first + # and then escaping UTF8 control chars (see clean_bin). + + if not isinstance(data, bytes): + raise ValueError("data must be bytes, but is {}".format(data.__class__.__name__)) + # We always insert a double-quote here so that we get a single-quoted string back + # https://stackoverflow.com/questions/29019340/why-does-python-use-different-quotes-for-representing-strings-depending-on-their + return repr(b'"' + data).lstrip("b")[2:-1] + + +def escaped_str_to_bytes(data): + """ + Take an escaped string and return the unescaped bytes equivalent. + """ + if not isinstance(data, six.string_types): + if six.PY2: + raise ValueError("data must be str or unicode, but is {}".format(data.__class__.__name__)) + raise ValueError("data must be str, but is {}".format(data.__class__.__name__)) + + if six.PY2: + if isinstance(data, unicode): + data = data.encode("utf8") + return data.decode("string-escape") + + # This one is difficult - we use an undocumented Python API here + # as per http://stackoverflow.com/a/23151714/934719 + return codecs.escape_decode(data)[0] + + +def isBin(s): + """ + Does this string have any non-ASCII characters? + """ + for i in s: + i = ord(i) + if i < 9 or 13 < i < 32 or 126 < i: + return True + return False + + +def isMostlyBin(s): + s = s[:100] + return sum(isBin(ch) for ch in s) / len(s) > 0.3 + + +def isXML(s): + for i in s: + if i in "\n \t": + continue + elif i == "<": + return True + else: + return False + + +def clean_hanging_newline(t): + """ + Many editors will silently add a newline to the final line of a + document (I'm looking at you, Vim). This function fixes this common + problem at the risk of removing a hanging newline in the rare cases + where the user actually intends it. + """ + if t and t[-1] == "\n": + return t[:-1] + return t + + +def hexdump(s): + """ + Returns: + A generator of (offset, hex, str) tuples + """ + for i in range(0, len(s), 16): + offset = "{:0=10x}".format(i).encode() + part = s[i:i + 16] + x = b" ".join("{:0=2x}".format(i).encode() for i in six.iterbytes(part)) + x = x.ljust(47) # 16*2 + 15 + yield (offset, x, clean_bin(part, False)) diff --git a/netlib/tcp.py b/netlib/tcp.py index d26bb5f7..de12102e 100644 --- a/netlib/tcp.py +++ b/netlib/tcp.py @@ -6,6 +6,7 @@ import sys import threading import time import traceback +import contextlib import binascii from six.moves import range @@ -16,13 +17,10 @@ import six import OpenSSL from OpenSSL import SSL -from . import certutils, version_check, utils +from netlib import certutils, version_check, basetypes, exceptions # This is a rather hackish way to make sure that # the latest version of pyOpenSSL is actually installed. -from netlib.exceptions import InvalidCertificateException, TcpReadIncomplete, TlsException, \ - TcpTimeout, TcpDisconnect, TcpException - version_check.check_pyopenssl_version() if six.PY2: @@ -71,6 +69,7 @@ sslversion_choices = { "TLSv1_2": (SSL.TLSv1_2_METHOD, SSL_BASIC_OPTIONS), } + class SSLKeyLogger(object): def __init__(self, filename): @@ -161,17 +160,17 @@ class Writer(_FileLike): def flush(self): """ - May raise TcpDisconnect + May raise exceptions.TcpDisconnect """ if hasattr(self.o, "flush"): try: self.o.flush() except (socket.error, IOError) as v: - raise TcpDisconnect(str(v)) + raise exceptions.TcpDisconnect(str(v)) def write(self, v): """ - May raise TcpDisconnect + May raise exceptions.TcpDisconnect """ if v: self.first_byte_timestamp = self.first_byte_timestamp or time.time() @@ -184,7 +183,7 @@ class Writer(_FileLike): self.add_log(v[:r]) return r except (SSL.Error, socket.error) as e: - raise TcpDisconnect(str(e)) + raise exceptions.TcpDisconnect(str(e)) class Reader(_FileLike): @@ -215,17 +214,17 @@ class Reader(_FileLike): time.sleep(0.1) continue else: - raise TcpTimeout() + raise exceptions.TcpTimeout() except socket.timeout: - raise TcpTimeout() + raise exceptions.TcpTimeout() except socket.error as e: - raise TcpDisconnect(str(e)) + raise exceptions.TcpDisconnect(str(e)) except SSL.SysCallError as e: if e.args == (-1, 'Unexpected EOF'): break - raise TlsException(str(e)) + raise exceptions.TlsException(str(e)) except SSL.Error as e: - raise TlsException(str(e)) + raise exceptions.TlsException(str(e)) self.first_byte_timestamp = self.first_byte_timestamp or time.time() if not data: break @@ -259,9 +258,9 @@ class Reader(_FileLike): result = self.read(length) if length != -1 and len(result) != length: if not result: - raise TcpDisconnect() + raise exceptions.TcpDisconnect() else: - raise TcpReadIncomplete( + raise exceptions.TcpReadIncomplete( "Expected %s bytes, got %s" % (length, len(result)) ) return result @@ -274,7 +273,7 @@ class Reader(_FileLike): Up to the next N bytes if peeking is successful. Raises: - TcpException if there was an error with the socket + exceptions.TcpException if there was an error with the socket TlsException if there was an error with pyOpenSSL. NotImplementedError if the underlying file object is not a [pyOpenSSL] socket """ @@ -282,7 +281,7 @@ class Reader(_FileLike): try: return self.o._sock.recv(length, socket.MSG_PEEK) except socket.error as e: - raise TcpException(repr(e)) + raise exceptions.TcpException(repr(e)) elif isinstance(self.o, SSL.Connection): try: if tuple(int(x) for x in OpenSSL.__version__.split(".")[:2]) > (0, 15): @@ -296,12 +295,12 @@ class Reader(_FileLike): self.o._raise_ssl_error(self.o._ssl, result) return SSL._ffi.buffer(buf, result)[:] except SSL.Error as e: - six.reraise(TlsException, TlsException(str(e)), sys.exc_info()[2]) + six.reraise(exceptions.TlsException, exceptions.TlsException(str(e)), sys.exc_info()[2]) else: raise NotImplementedError("Can only peek into (pyOpenSSL) sockets") -class Address(utils.Serializable): +class Address(basetypes.Serializable): """ This class wraps an IPv4/IPv6 tuple to provide named attributes and @@ -489,7 +488,7 @@ class _Connection(object): try: self.wfile.flush() self.wfile.close() - except TcpDisconnect: + except exceptions.TcpDisconnect: pass self.rfile.close() @@ -553,7 +552,7 @@ class _Connection(object): # TODO: maybe change this to with newer pyOpenSSL APIs context.set_tmp_ecdh(OpenSSL.crypto.get_elliptic_curve('prime256v1')) except SSL.Error as v: - raise TlsException("SSL cipher specification error: %s" % str(v)) + raise exceptions.TlsException("SSL cipher specification error: %s" % str(v)) # SSLKEYLOGFILE if log_ssl_key: @@ -574,11 +573,17 @@ class _Connection(object): elif alpn_select_callback is not None and alpn_select is None: context.set_alpn_select_callback(alpn_select_callback) elif alpn_select_callback is not None and alpn_select is not None: - raise TlsException("ALPN error: only define alpn_select (string) OR alpn_select_callback (method).") + raise exceptions.TlsException("ALPN error: only define alpn_select (string) OR alpn_select_callback (method).") return context +@contextlib.contextmanager +def _closer(client): + yield + client.close() + + class TCPClient(_Connection): def __init__(self, address, source_address=None): @@ -631,7 +636,7 @@ class TCPClient(_Connection): context.use_privatekey_file(cert) context.use_certificate_file(cert) except SSL.Error as v: - raise TlsException("SSL client certificate error: %s" % str(v)) + raise exceptions.TlsException("SSL client certificate error: %s" % str(v)) return context def convert_to_ssl(self, sni=None, alpn_protos=None, **sslctx_kwargs): @@ -645,7 +650,7 @@ class TCPClient(_Connection): """ verification_mode = sslctx_kwargs.get('verify_options', None) if verification_mode == SSL.VERIFY_PEER and not sni: - raise TlsException("Cannot validate certificate hostname without SNI") + raise exceptions.TlsException("Cannot validate certificate hostname without SNI") context = self.create_ssl_context( alpn_protos=alpn_protos, @@ -660,14 +665,14 @@ class TCPClient(_Connection): self.connection.do_handshake() except SSL.Error as v: if self.ssl_verification_error: - raise InvalidCertificateException("SSL handshake error: %s" % repr(v)) + raise exceptions.InvalidCertificateException("SSL handshake error: %s" % repr(v)) else: - raise TlsException("SSL handshake error: %s" % repr(v)) + raise exceptions.TlsException("SSL handshake error: %s" % repr(v)) else: # Fix for pre v1.0 OpenSSL, which doesn't throw an exception on # certificate validation failure if verification_mode == SSL.VERIFY_PEER and self.ssl_verification_error is not None: - raise InvalidCertificateException("SSL handshake error: certificate verify failed") + raise exceptions.InvalidCertificateException("SSL handshake error: certificate verify failed") self.cert = certutils.SSLCert(self.connection.get_peer_certificate()) @@ -690,7 +695,7 @@ class TCPClient(_Connection): except (ValueError, ssl_match_hostname.CertificateError) as e: self.ssl_verification_error = dict(depth=0, errno="Invalid Hostname") if verification_mode == SSL.VERIFY_PEER: - raise InvalidCertificateException("Presented certificate for {} is not valid: {}".format(sni, str(e))) + raise exceptions.InvalidCertificateException("Presented certificate for {} is not valid: {}".format(sni, str(e))) self.ssl_established = True self.rfile.set_descriptor(self.connection) @@ -704,12 +709,13 @@ class TCPClient(_Connection): connection.connect(self.address()) self.source_address = Address(connection.getsockname()) except (socket.error, IOError) as err: - raise TcpException( + raise exceptions.TcpException( 'Error connecting to "%s": %s' % (self.address.host, err)) self.connection = connection self.ip_address = Address(connection.getpeername()) self._makefile() + return _closer(self) def settimeout(self, n): self.connection.settimeout(n) @@ -817,7 +823,7 @@ class BaseHandler(_Connection): try: self.connection.do_handshake() except SSL.Error as v: - raise TlsException("SSL handshake error: %s" % repr(v)) + raise exceptions.TlsException("SSL handshake error: %s" % repr(v)) self.ssl_established = True self.rfile.set_descriptor(self.connection) self.wfile.set_descriptor(self.connection) @@ -835,6 +841,25 @@ class BaseHandler(_Connection): return b"" +class Counter: + def __init__(self): + self._count = 0 + self._lock = threading.Lock() + + @property + def count(self): + with self._lock: + return self._count + + def __enter__(self): + with self._lock: + self._count += 1 + + def __exit__(self, *args): + with self._lock: + self._count -= 1 + + class TCPServer(object): request_queue_size = 20 @@ -847,15 +872,17 @@ class TCPServer(object): self.socket.bind(self.address()) self.address = Address.wrap(self.socket.getsockname()) self.socket.listen(self.request_queue_size) + self.handler_counter = Counter() def connection_thread(self, connection, client_address): - client_address = Address(client_address) - try: - self.handle_client_connection(connection, client_address) - except: - self.handle_error(connection, client_address) - finally: - close_socket(connection) + with self.handler_counter: + client_address = Address(client_address) + try: + self.handle_client_connection(connection, client_address) + except: + self.handle_error(connection, client_address) + finally: + close_socket(connection) def serve_forever(self, poll_interval=0.1): self.__is_shut_down.clear() @@ -900,7 +927,7 @@ class TCPServer(object): """ # If a thread has persisted after interpreter exit, the module might be # none. - if traceback: + if traceback and six: exc = six.text_type(traceback.format_exc()) print(u'-' * 40, file=fp) print( diff --git a/netlib/tutils.py b/netlib/tutils.py index 18d632f0..452766d6 100644 --- a/netlib/tutils.py +++ b/netlib/tutils.py @@ -7,8 +7,7 @@ from contextlib import contextmanager import six import sys -from . import utils, tcp -from .http import Request, Response, Headers +from netlib import utils, tcp, http def treader(bytes): @@ -91,8 +90,7 @@ class RaisesContext(object): test_data = utils.Data(__name__) # FIXME: Temporary workaround during repo merge. -import os -test_data.dirname = os.path.join(test_data.dirname,"..","test","netlib") +test_data.dirname = os.path.join(test_data.dirname, "..", "test", "netlib") def treq(**kwargs): @@ -108,11 +106,11 @@ def treq(**kwargs): port=22, path=b"/path", http_version=b"HTTP/1.1", - headers=Headers(header="qvalue", content_length="7"), + headers=http.Headers(((b"header", b"qvalue"), (b"content-length", b"7"))), content=b"content" ) default.update(kwargs) - return Request(**default) + return http.Request(**default) def tresp(**kwargs): @@ -124,10 +122,10 @@ def tresp(**kwargs): http_version=b"HTTP/1.1", status_code=200, reason=b"OK", - headers=Headers(header_response="svalue", content_length="7"), + headers=http.Headers(((b"header-response", b"svalue"), (b"content-length", b"7"))), content=b"message", timestamp_start=time.time(), timestamp_end=time.time(), ) default.update(kwargs) - return Response(**default) + return http.Response(**default) diff --git a/netlib/utils.py b/netlib/utils.py index 7499f71f..b4b99679 100644 --- a/netlib/utils.py +++ b/netlib/utils.py @@ -1,121 +1,11 @@ from __future__ import absolute_import, print_function, division import os.path import re -import codecs -import unicodedata -from abc import ABCMeta, abstractmethod import importlib import inspect import six -from six.moves import urllib -import hyperframe - - -@six.add_metaclass(ABCMeta) -class Serializable(object): - """ - Abstract Base Class that defines an API to save an object's state and restore it later on. - """ - - @classmethod - @abstractmethod - def from_state(cls, state): - """ - Create a new object from the given state. - """ - raise NotImplementedError() - - @abstractmethod - def get_state(self): - """ - Retrieve object state. - """ - raise NotImplementedError() - - @abstractmethod - def set_state(self, state): - """ - Set object state to the given state. - """ - raise NotImplementedError() - - def copy(self): - return self.from_state(self.get_state()) - - -def always_bytes(unicode_or_bytes, *encode_args): - if isinstance(unicode_or_bytes, six.text_type): - return unicode_or_bytes.encode(*encode_args) - return unicode_or_bytes - - -def native(s, *encoding_opts): - """ - Convert :py:class:`bytes` or :py:class:`unicode` to the native - :py:class:`str` type, using latin1 encoding if conversion is necessary. - - https://www.python.org/dev/peps/pep-3333/#a-note-on-string-types - """ - if not isinstance(s, (six.binary_type, six.text_type)): - raise TypeError("%r is neither bytes nor unicode" % s) - if six.PY3: - if isinstance(s, six.binary_type): - return s.decode(*encoding_opts) - else: - if isinstance(s, six.text_type): - return s.encode(*encoding_opts) - return s - - -def isascii(bytes): - try: - bytes.decode("ascii") - except ValueError: - return False - return True - - -def clean_bin(s, keep_spacing=True): - """ - Cleans binary data to make it safe to display. - - Args: - keep_spacing: If False, tabs and newlines will also be replaced. - """ - if isinstance(s, six.text_type): - if keep_spacing: - keep = u" \n\r\t" - else: - keep = u" " - return u"".join( - ch if (unicodedata.category(ch)[0] not in "CZ" or ch in keep) else u"." - for ch in s - ) - else: - if keep_spacing: - keep = (9, 10, 13) # \t, \n, \r, - else: - keep = () - return b"".join( - six.int2byte(ch) if (31 < ch < 127 or ch in keep) else b"." - for ch in six.iterbytes(s) - ) - - -def hexdump(s): - """ - Returns: - A generator of (offset, hex, str) tuples - """ - for i in range(0, len(s), 16): - offset = "{:0=10x}".format(i).encode() - part = s[i:i + 16] - x = b" ".join("{:0=2x}".format(i).encode() for i in six.iterbytes(part)) - x = x.ljust(47) # 16*2 + 15 - yield (offset, x, clean_bin(part, False)) - def setbit(byte, offset, value): """ @@ -161,22 +51,6 @@ class BiDi(object): return self.values.get(n, default) -def pretty_size(size): - suffixes = [ - ("B", 2 ** 10), - ("kB", 2 ** 20), - ("MB", 2 ** 30), - ] - for suf, lim in suffixes: - if size >= lim: - continue - else: - x = round(size / float(lim / 2 ** 10), 2) - if x == int(x): - x = int(x) - return str(x) + suf - - class Data(object): def __init__(self, name): @@ -222,83 +96,6 @@ def is_valid_port(port): return 0 <= port <= 65535 -# PY2 workaround -def decode_parse_result(result, enc): - if hasattr(result, "decode"): - return result.decode(enc) - else: - return urllib.parse.ParseResult(*[x.decode(enc) for x in result]) - - -# PY2 workaround -def encode_parse_result(result, enc): - if hasattr(result, "encode"): - return result.encode(enc) - else: - return urllib.parse.ParseResult(*[x.encode(enc) for x in result]) - - -def parse_url(url): - """ - URL-parsing function that checks that - - port is an integer 0-65535 - - host is a valid IDNA-encoded hostname with no null-bytes - - path is valid ASCII - - Args: - A URL (as bytes or as unicode) - - Returns: - A (scheme, host, port, path) tuple - - Raises: - ValueError, if the URL is not properly formatted. - """ - parsed = urllib.parse.urlparse(url) - - if not parsed.hostname: - raise ValueError("No hostname given") - - if isinstance(url, six.binary_type): - host = parsed.hostname - - # this should not raise a ValueError, - # but we try to be very forgiving here and accept just everything. - # decode_parse_result(parsed, "ascii") - else: - host = parsed.hostname.encode("idna") - parsed = encode_parse_result(parsed, "ascii") - - port = parsed.port - if not port: - port = 443 if parsed.scheme == b"https" else 80 - - full_path = urllib.parse.urlunparse( - (b"", b"", parsed.path, parsed.params, parsed.query, parsed.fragment) - ) - if not full_path.startswith(b"/"): - full_path = b"/" + full_path - - if not is_valid_host(host): - raise ValueError("Invalid Host") - if not is_valid_port(port): - raise ValueError("Invalid Port") - - return parsed.scheme, host, port, full_path - - -def get_header_tokens(headers, key): - """ - Retrieve all tokens for a header key. A number of different headers - follow a pattern where each header line can containe comma-separated - tokens, and headers can be set multiple times. - """ - if key not in headers: - return [] - tokens = headers[key].split(",") - return [token.strip() for token in tokens] - - def hostport(scheme, host, port): """ Returns the host component, with a port specifcation if needed. @@ -310,141 +107,3 @@ def hostport(scheme, host, port): return b"%s:%d" % (host, port) else: return "%s:%d" % (host, port) - - -def unparse_url(scheme, host, port, path=""): - """ - Returns a URL string, constructed from the specified components. - - Args: - All args must be str. - """ - if path == "*": - path = "" - return "%s://%s%s" % (scheme, hostport(scheme, host, port), path) - - -def urlencode(s): - """ - Takes a list of (key, value) tuples and returns a urlencoded string. - """ - s = [tuple(i) for i in s] - return urllib.parse.urlencode(s, False) - - -def urldecode(s): - """ - Takes a urlencoded string and returns a list of (key, value) tuples. - """ - return urllib.parse.parse_qsl(s, keep_blank_values=True) - - -def parse_content_type(c): - """ - A simple parser for content-type values. Returns a (type, subtype, - parameters) tuple, where type and subtype are strings, and parameters - is a dict. If the string could not be parsed, return None. - - E.g. the following string: - - text/html; charset=UTF-8 - - Returns: - - ("text", "html", {"charset": "UTF-8"}) - """ - parts = c.split(";", 1) - ts = parts[0].split("/", 1) - if len(ts) != 2: - return None - d = {} - if len(parts) == 2: - for i in parts[1].split(";"): - clause = i.split("=", 1) - if len(clause) == 2: - d[clause[0].strip()] = clause[1].strip() - return ts[0].lower(), ts[1].lower(), d - - -def multipartdecode(headers, content): - """ - Takes a multipart boundary encoded string and returns list of (key, value) tuples. - """ - v = headers.get("content-type") - if v: - v = parse_content_type(v) - if not v: - return [] - try: - boundary = v[2]["boundary"].encode("ascii") - except (KeyError, UnicodeError): - return [] - - rx = re.compile(br'\bname="([^"]+)"') - r = [] - - for i in content.split(b"--" + boundary): - parts = i.splitlines() - if len(parts) > 1 and parts[0][0:2] != b"--": - match = rx.search(parts[1]) - if match: - key = match.group(1) - value = b"".join(parts[3 + parts[2:].index(b""):]) - r.append((key, value)) - return r - return [] - - -def http2_read_raw_frame(rfile): - header = rfile.safe_read(9) - length = int(codecs.encode(header[:3], 'hex_codec'), 16) - - if length == 4740180: - raise ValueError("Length field looks more like HTTP/1.1: %s" % rfile.peek(20)) - - body = rfile.safe_read(length) - return [header, body] - - -def http2_read_frame(rfile): - header, body = http2_read_raw_frame(rfile) - frame, length = hyperframe.frame.Frame.parse_frame_header(header) - frame.parse_body(memoryview(body)) - return frame - - -def safe_subn(pattern, repl, target, *args, **kwargs): - """ - There are Unicode conversion problems with re.subn. We try to smooth - that over by casting the pattern and replacement to strings. We really - need a better solution that is aware of the actual content ecoding. - """ - return re.subn(str(pattern), str(repl), target, *args, **kwargs) - - -def bytes_to_escaped_str(data): - """ - Take bytes and return a safe string that can be displayed to the user. - """ - # TODO: We may want to support multi-byte characters without escaping them. - # One way to do would be calling .decode("utf8", "backslashreplace") first - # and then escaping UTF8 control chars (see clean_bin). - - if not isinstance(data, bytes): - raise ValueError("data must be bytes") - return repr(data).lstrip("b")[1:-1] - - -def escaped_str_to_bytes(data): - """ - Take an escaped string and return the unescaped bytes equivalent. - """ - if not isinstance(data, str): - raise ValueError("data must be str") - - if six.PY2: - return data.decode("string-escape") - - # This one is difficult - we use an undocumented Python API here - # as per http://stackoverflow.com/a/23151714/934719 - return codecs.escape_decode(data)[0] diff --git a/netlib/version_check.py b/netlib/version_check.py index 8e05b458..63f3e876 100644 --- a/netlib/version_check.py +++ b/netlib/version_check.py @@ -10,7 +10,6 @@ import os.path import six import OpenSSL -from . import version PYOPENSSL_MIN_VERSION = (0, 15) diff --git a/netlib/websockets/__init__.py b/netlib/websockets/__init__.py index 1c143919..fea696d9 100644 --- a/netlib/websockets/__init__.py +++ b/netlib/websockets/__init__.py @@ -1,2 +1,11 @@ -from .frame import * -from .protocol import * +from __future__ import absolute_import, print_function, division +from .frame import FrameHeader, Frame, OPCODE +from .protocol import Masker, WebsocketsProtocol + +__all__ = [ + "FrameHeader", + "Frame", + "Masker", + "WebsocketsProtocol", + "OPCODE", +] diff --git a/netlib/websockets/frame.py b/netlib/websockets/frame.py index fce2c9d3..42196ffb 100644 --- a/netlib/websockets/frame.py +++ b/netlib/websockets/frame.py @@ -6,15 +6,17 @@ import warnings import six -from .protocol import Masker from netlib import tcp +from netlib import strutils from netlib import utils +from netlib import human +from netlib.websockets import protocol MAX_16_BIT_INT = (1 << 16) MAX_64_BIT_INT = (1 << 64) -DEFAULT=object() +DEFAULT = object() OPCODE = utils.BiDi( CONTINUE=0x00, @@ -98,7 +100,7 @@ class FrameHeader(object): if self.masking_key: vals.append(":key=%s" % repr(self.masking_key)) if self.payload_length: - vals.append(" %s" % utils.pretty_size(self.payload_length)) + vals.append(" %s" % human.pretty_size(self.payload_length)) return "".join(vals) def human_readable(self): @@ -253,7 +255,7 @@ class Frame(object): def __repr__(self): ret = repr(self.header) if self.payload: - ret = ret + "\nPayload:\n" + utils.clean_bin(self.payload).decode("ascii") + ret = ret + "\nPayload:\n" + strutils.clean_bin(self.payload).decode("ascii") return ret def human_readable(self): @@ -266,7 +268,7 @@ class Frame(object): """ b = bytes(self.header) if self.header.masking_key: - b += Masker(self.header.masking_key)(self.payload) + b += protocol.Masker(self.header.masking_key)(self.payload) else: b += self.payload return b @@ -295,7 +297,7 @@ class Frame(object): payload = fp.safe_read(header.payload_length) if header.mask == 1 and header.masking_key: - payload = Masker(header.masking_key)(payload) + payload = protocol.Masker(header.masking_key)(payload) return cls( payload, diff --git a/netlib/websockets/protocol.py b/netlib/websockets/protocol.py index 1e95fa1c..c1b7be2c 100644 --- a/netlib/websockets/protocol.py +++ b/netlib/websockets/protocol.py @@ -1,26 +1,26 @@ +""" +Colleciton of utility functions that implement small portions of the RFC6455 +WebSockets Protocol Useful for building WebSocket clients and servers. +Emphassis is on readabilty, simplicity and modularity, not performance or +completeness +This is a work in progress and does not yet contain all the utilites need to +create fully complient client/servers # +Spec: https://tools.ietf.org/html/rfc6455 -# Colleciton of utility functions that implement small portions of the RFC6455 -# WebSockets Protocol Useful for building WebSocket clients and servers. -# -# Emphassis is on readabilty, simplicity and modularity, not performance or -# completeness -# -# This is a work in progress and does not yet contain all the utilites need to -# create fully complient client/servers # -# Spec: https://tools.ietf.org/html/rfc6455 +The magic sha that websocket servers must know to prove they understand +RFC6455 +""" -# The magic sha that websocket servers must know to prove they understand -# RFC6455 from __future__ import absolute_import import base64 import hashlib import os -import binascii import six -from ..http import Headers + +from netlib import http websockets_magic = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11' VERSION = "13" @@ -73,11 +73,11 @@ class WebsocketsProtocol(object): specified, it is generated, and can be found in sec-websocket-key in the returned header set. - Returns an instance of Headers + Returns an instance of http.Headers """ if not key: key = base64.b64encode(os.urandom(16)).decode('ascii') - return Headers( + return http.Headers( sec_websocket_key=key, sec_websocket_version=version, connection="Upgrade", @@ -89,27 +89,24 @@ class WebsocketsProtocol(object): """ The server response is a valid HTTP 101 response. """ - return Headers( + return http.Headers( sec_websocket_accept=self.create_server_nonce(key), connection="Upgrade", upgrade="websocket" ) - @classmethod def check_client_handshake(self, headers): if headers.get("upgrade") != "websocket": return return headers.get("sec-websocket-key") - @classmethod def check_server_handshake(self, headers): if headers.get("upgrade") != "websocket": return return headers.get("sec-websocket-accept") - @classmethod def create_server_nonce(self, client_nonce): return base64.b64encode(hashlib.sha1(client_nonce + websockets_magic).digest()) diff --git a/netlib/wsgi.py b/netlib/wsgi.py index d6dfae5d..c66fddc2 100644 --- a/netlib/wsgi.py +++ b/netlib/wsgi.py @@ -1,14 +1,13 @@ from __future__ import (absolute_import, print_function, division) -from io import BytesIO, StringIO -import urllib + import time import traceback - import six +from io import BytesIO from six.moves import urllib -from netlib.utils import always_bytes, native -from . import http, tcp +from netlib import http, tcp, strutils + class ClientConn(object): @@ -55,38 +54,38 @@ class WSGIAdaptor(object): self.app, self.domain, self.port, self.sversion = app, domain, port, sversion def make_environ(self, flow, errsoc, **extra): - path = native(flow.request.path, "latin-1") + path = strutils.native(flow.request.path, "latin-1") if '?' in path: - path_info, query = native(path, "latin-1").split('?', 1) + path_info, query = strutils.native(path, "latin-1").split('?', 1) else: path_info = path query = '' environ = { 'wsgi.version': (1, 0), - 'wsgi.url_scheme': native(flow.request.scheme, "latin-1"), + 'wsgi.url_scheme': strutils.native(flow.request.scheme, "latin-1"), 'wsgi.input': BytesIO(flow.request.content or b""), 'wsgi.errors': errsoc, 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'SERVER_SOFTWARE': self.sversion, - 'REQUEST_METHOD': native(flow.request.method, "latin-1"), + 'REQUEST_METHOD': strutils.native(flow.request.method, "latin-1"), 'SCRIPT_NAME': '', 'PATH_INFO': urllib.parse.unquote(path_info), 'QUERY_STRING': query, - 'CONTENT_TYPE': native(flow.request.headers.get('Content-Type', ''), "latin-1"), - 'CONTENT_LENGTH': native(flow.request.headers.get('Content-Length', ''), "latin-1"), + 'CONTENT_TYPE': strutils.native(flow.request.headers.get('Content-Type', ''), "latin-1"), + 'CONTENT_LENGTH': strutils.native(flow.request.headers.get('Content-Length', ''), "latin-1"), 'SERVER_NAME': self.domain, 'SERVER_PORT': str(self.port), - 'SERVER_PROTOCOL': native(flow.request.http_version, "latin-1"), + 'SERVER_PROTOCOL': strutils.native(flow.request.http_version, "latin-1"), } environ.update(extra) if flow.client_conn.address: - environ["REMOTE_ADDR"] = native(flow.client_conn.address.host, "latin-1") + environ["REMOTE_ADDR"] = strutils.native(flow.client_conn.address.host, "latin-1") environ["REMOTE_PORT"] = flow.client_conn.address.port for key, value in flow.request.headers.items(): - key = 'HTTP_' + native(key, "latin-1").upper().replace('-', '_') + key = 'HTTP_' + strutils.native(key, "latin-1").upper().replace('-', '_') if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'): environ[key] = value return environ @@ -140,7 +139,7 @@ class WSGIAdaptor(object): elif state["status"]: raise AssertionError('Response already started') state["status"] = status - state["headers"] = http.Headers([[always_bytes(k), always_bytes(v)] for k,v in headers]) + state["headers"] = http.Headers([[strutils.always_bytes(k), strutils.always_bytes(v)] for k, v in headers]) if exc_info: self.error_page(soc, state["headers_sent"], traceback.format_tb(exc_info[2])) state["headers_sent"] = True @@ -154,7 +153,7 @@ class WSGIAdaptor(object): write(i) if not state["headers_sent"]: write(b"") - except Exception as e: + except Exception: try: s = traceback.format_exc() errs.write(s.encode("utf-8", "replace")) diff --git a/pathod/app.py b/pathod/app.py index aa00ed69..e3216c58 100644 --- a/pathod/app.py +++ b/pathod/app.py @@ -1,10 +1,11 @@ import logging import pprint -from six.moves import cStringIO as StringIO +import io import copy from flask import Flask, jsonify, render_template, request, abort, make_response -from . import version, language, utils +from . import version, language from netlib.http import user_agents +from netlib import strutils logging.basicConfig(level="DEBUG") EXAMPLE_HOST = "example.com" @@ -145,7 +146,7 @@ def make_app(noapi, debug): args["marked"] = v.marked() return render(template, False, **args) - s = StringIO() + s = io.BytesIO() settings = copy.copy(app.config["pathod"].settings) settings.request_host = EXAMPLE_HOST @@ -166,7 +167,7 @@ def make_app(noapi, debug): settings.websocket_key = EXAMPLE_WEBSOCKET_KEY language.serve(safe, s, settings) - args["output"] = utils.escape_unprintables(s.getvalue()) + args["output"] = strutils.bytes_to_escaped_str(s.getvalue()) return render(template, False, **args) @app.route('/response_preview') diff --git a/pathod/language/__init__.py b/pathod/language/__init__.py index 32199e08..0841196e 100644 --- a/pathod/language/__init__.py +++ b/pathod/language/__init__.py @@ -1,19 +1,26 @@ +from __future__ import absolute_import + import itertools import time +from six.moves import range import pyparsing as pp from . import http, http2, websockets, writer, exceptions -from exceptions import * -from base import Settings -assert Settings # prevent pyflakes from messing with this +from .exceptions import RenderError, FileAccessDenied, ParseException +from .base import Settings + +__all__ = [ + "RenderError", "FileAccessDenied", "ParseException", + "Settings", +] def expand(msg): times = getattr(msg, "times", None) if times: - for j_ in xrange(int(times.value)): + for j_ in range(int(times.value)): yield msg.strike_token("times") else: yield msg diff --git a/pathod/language/base.py b/pathod/language/base.py index a4302998..11ee0623 100644 --- a/pathod/language/base.py +++ b/pathod/language/base.py @@ -3,9 +3,14 @@ import os import abc import pyparsing as pp -from .. import utils +import six +from six.moves import reduce +from netlib import strutils +from netlib import human + from . import generators, exceptions + class Settings(object): def __init__( @@ -105,7 +110,7 @@ class Token(object): class _TokValueLiteral(Token): def __init__(self, val): - self.val = val.decode("string_escape") + self.val = strutils.escaped_str_to_bytes(val) def get_generator(self, settings_): return self.val @@ -130,7 +135,7 @@ class TokValueLiteral(_TokValueLiteral): return v def spec(self): - inner = self.val.encode("string_escape") + inner = strutils.bytes_to_escaped_str(self.val) inner = inner.replace(r"\'", r"\x27") return "'" + inner + "'" @@ -143,7 +148,7 @@ class TokValueNakedLiteral(_TokValueLiteral): return e.setParseAction(lambda x: cls(*x)) def spec(self): - return self.val.encode("string_escape") + return strutils.bytes_to_escaped_str(self.val) class TokValueGenerate(Token): @@ -154,14 +159,14 @@ class TokValueGenerate(Token): self.usize, self.unit, self.datatype = usize, unit, datatype def bytes(self): - return self.usize * utils.SIZE_UNITS[self.unit] + return self.usize * human.SIZE_UNITS[self.unit] def get_generator(self, settings_): return generators.RandomGenerator(self.datatype, self.bytes()) def freeze(self, settings): g = self.get_generator(settings) - return TokValueLiteral(g[:].encode("string_escape")) + return TokValueLiteral(strutils.bytes_to_escaped_str(g[:])) @classmethod def expr(cls): @@ -169,7 +174,7 @@ class TokValueGenerate(Token): u = reduce( operator.or_, - [pp.Literal(i) for i in utils.SIZE_UNITS.keys()] + [pp.Literal(i) for i in human.SIZE_UNITS.keys()] ).leaveWhitespace() e = e + pp.Optional(u, default=None) @@ -221,7 +226,7 @@ class TokValueFile(Token): return generators.FileGenerator(s) def spec(self): - return "<'%s'" % self.path.encode("string_escape") + return "<'%s'" % strutils.bytes_to_escaped_str(self.path) TokValue = pp.MatchFirst( @@ -337,7 +342,7 @@ class OptionsOrValue(_Component): # it to be canonical. The user can specify a different case by using a # string value literal. self.option_used = False - if isinstance(value, basestring): + if isinstance(value, six.string_types): for i in self.options: # Find the exact option value in a case-insensitive way if i.lower() == value.lower(): @@ -573,4 +578,4 @@ class NestedMessage(Token): def freeze(self, settings): f = self.parsed.freeze(settings).spec() - return self.__class__(TokValueLiteral(f.encode("string_escape"))) + return self.__class__(TokValueLiteral(strutils.bytes_to_escaped_str(f))) diff --git a/pathod/language/exceptions.py b/pathod/language/exceptions.py index 84ad3c02..eb22bed2 100644 --- a/pathod/language/exceptions.py +++ b/pathod/language/exceptions.py @@ -1,4 +1,3 @@ - class RenderError(Exception): pass diff --git a/pathod/language/generators.py b/pathod/language/generators.py index a17e7052..9fff3082 100644 --- a/pathod/language/generators.py +++ b/pathod/language/generators.py @@ -2,17 +2,19 @@ import string import random import mmap +import six + DATATYPES = dict( - ascii_letters=string.ascii_letters, - ascii_lowercase=string.ascii_lowercase, - ascii_uppercase=string.ascii_uppercase, - digits=string.digits, - hexdigits=string.hexdigits, - octdigits=string.octdigits, - punctuation=string.punctuation, - whitespace=string.whitespace, - ascii=string.printable, - bytes="".join(chr(i) for i in range(256)) + ascii_letters=string.ascii_letters.encode(), + ascii_lowercase=string.ascii_lowercase.encode(), + ascii_uppercase=string.ascii_uppercase.encode(), + digits=string.digits.encode(), + hexdigits=string.hexdigits.encode(), + octdigits=string.octdigits.encode(), + punctuation=string.punctuation.encode(), + whitespace=string.whitespace.encode(), + ascii=string.printable.encode(), + bytes=bytes(bytearray(range(256))) ) @@ -35,16 +37,25 @@ class TransformGenerator(object): def __getitem__(self, x): d = self.gen.__getitem__(x) + if isinstance(x, slice): + return self.transform(x.start, d) return self.transform(x, d) - def __getslice__(self, a, b): - d = self.gen.__getslice__(a, b) - return self.transform(a, d) - def __repr__(self): return "'transform(%s)'" % self.gen +def rand_byte(chars): + """ + Return a random character as byte from a charset. + """ + # bytearray has consistent behaviour on both Python 2 and 3 + # while bytes does not + if six.PY2: + return random.choice(chars) + return bytes([random.choice(chars)]) + + class RandomGenerator(object): def __init__(self, dtype, length): @@ -55,12 +66,10 @@ class RandomGenerator(object): return self.length def __getitem__(self, x): - return random.choice(DATATYPES[self.dtype]) - - def __getslice__(self, a, b): - b = min(b, self.length) chars = DATATYPES[self.dtype] - return "".join(random.choice(chars) for x in range(a, b)) + if isinstance(x, slice): + return b"".join(rand_byte(chars) for _ in range(*x.indices(self.length))) + return rand_byte(chars) def __repr__(self): return "%s random from %s" % (self.length, self.dtype) @@ -70,17 +79,17 @@ class FileGenerator(object): def __init__(self, path): self.path = path - self.fp = file(path, "rb") + self.fp = open(path, "rb") self.map = mmap.mmap(self.fp.fileno(), 0, access=mmap.ACCESS_READ) def __len__(self): return len(self.map) def __getitem__(self, x): - return self.map.__getitem__(x) - - def __getslice__(self, a, b): - return self.map.__getslice__(a, b) + if isinstance(x, slice): + return self.map.__getitem__(x) + # A slice of length 1 returns a byte object (not an integer) + return self.map.__getitem__(slice(x, x + 1 or self.map.size())) def __repr__(self): return "<%s" % self.path diff --git a/pathod/language/http.py b/pathod/language/http.py index a82f12fe..b2308d5e 100644 --- a/pathod/language/http.py +++ b/pathod/language/http.py @@ -11,6 +11,7 @@ from . import base, exceptions, actions, message # instead of duplicating the HTTP on-the-wire representation here. # see http2 language for an example + class WS(base.CaselessLiteral): TOK = "ws" diff --git a/pathod/language/http2.py b/pathod/language/http2.py index d5e3ca31..85d9047f 100644 --- a/pathod/language/http2.py +++ b/pathod/language/http2.py @@ -27,6 +27,7 @@ from . import base, message h2f:42:DATA:END_STREAM,PADDED:0x1234567:'content body payload' """ + def get_header(val, headers): """ Header keys may be Values, so we have to "generate" them as we try the @@ -48,6 +49,7 @@ class _HeaderMixin(object): self.value.get_generator(settings), ) + class _HTTP2Message(message.Message): @property def actions(self): @@ -287,13 +289,10 @@ class Request(_HTTP2Message): def spec(self): return ":".join([i.spec() for i in self.tokens]) + def make_error_response(reason, body=None): tokens = [ StatusCode("800"), Body(base.TokValueLiteral("pathod error: " + (body or reason))), ] return Response(tokens) - - -# class Frame(message.Message): -# pass diff --git a/pathod/language/websockets.py b/pathod/language/websockets.py index 09443a95..9b752b7e 100644 --- a/pathod/language/websockets.py +++ b/pathod/language/websockets.py @@ -1,4 +1,3 @@ -import os import random import string import netlib.websockets diff --git a/pathod/language/writer.py b/pathod/language/writer.py index 1a27e1ef..22e32ce2 100644 --- a/pathod/language/writer.py +++ b/pathod/language/writer.py @@ -1,6 +1,5 @@ import time from netlib.exceptions import TcpDisconnect -import netlib.tcp BLOCKSIZE = 1024 # It's not clear what the upper limit for time.sleep is. It's lower than the diff --git a/pathod/log.py b/pathod/log.py index f203542f..5bf55de4 100644 --- a/pathod/log.py +++ b/pathod/log.py @@ -1,8 +1,8 @@ import datetime -import netlib.utils -import netlib.tcp -import netlib.http +import six + +from netlib import strutils TIMEFMT = '%d-%m-%y %H:%M:%S' @@ -53,17 +53,17 @@ class LogCtx(object): ] ) if exc_value: - raise exc_type, exc_value, traceback + six.reraise(exc_type, exc_value, traceback) def suppress(self): self.suppressed = True def dump(self, data, hexdump): if hexdump: - for line in netlib.utils.hexdump(data): + for line in strutils.hexdump(data): self("\t%s %s %s" % line) else: - for i in netlib.utils.clean_bin(data).split("\n"): + for i in strutils.clean_bin(data).split("\n"): self("\t%s" % i) def __call__(self, line): diff --git a/pathod/pathoc.py b/pathod/pathoc.py index a49ed351..5cfb4591 100644 --- a/pathod/pathoc.py +++ b/pathod/pathoc.py @@ -13,21 +13,24 @@ import threading import OpenSSL.crypto import six -from netlib import tcp, http, certutils, websockets, socks +from netlib import tcp, certutils, websockets, socks from netlib.exceptions import HttpException, TcpDisconnect, TcpTimeout, TlsException, TcpException, \ NetlibException from netlib.http import http1, http2 -import language.http -import language.websockets -from . import utils, log +from . import log, language import logging from netlib.tutils import treq +from netlib import strutils logging.getLogger("hpack").setLevel(logging.WARNING) +def xrepr(s): + return repr(s)[1:-1] + + class PathocError(Exception): pass @@ -43,7 +46,7 @@ class SSLInfo(object): "Cipher: %s, %s bit, %s" % self.cipher, "SSL certificate chain:" ] - for n,i in enumerate(self.certchain): + for n, i in enumerate(self.certchain): parts.append(" Certificate [%s]" % n) parts.append("\tSubject: ") for cn in i.get_subject().get_components(): @@ -74,7 +77,6 @@ class SSLInfo(object): return "\n".join(parts) - class WebsocketFrameReader(threading.Thread): def __init__( @@ -284,7 +286,7 @@ class Pathoc(tcp.TCPClient): if self.use_http2 and not self.ssl: raise NotImplementedError("HTTP2 without SSL is not supported.") - tcp.TCPClient.connect(self) + ret = tcp.TCPClient.connect(self) if connect_to: self.http_connect(connect_to) @@ -322,6 +324,7 @@ class Pathoc(tcp.TCPClient): if self.timeout: self.settimeout(self.timeout) + return ret def stop(self): if self.ws_framereader: @@ -426,7 +429,7 @@ class Pathoc(tcp.TCPClient): finally: if resp: lg("<< %s %s: %s bytes" % ( - resp.status_code, utils.xrepr(resp.reason), len(resp.content) + resp.status_code, strutils.bytes_to_escaped_str(resp.reason), len(resp.content) )) if resp.status_code in self.ignorecodes: lg.suppress() diff --git a/pathod/pathod.py b/pathod/pathod.py index 017ce072..0449c0c1 100644 --- a/pathod/pathod.py +++ b/pathod/pathod.py @@ -6,15 +6,11 @@ import sys import threading import urllib -from netlib import tcp, http, certutils, websockets +from netlib import tcp, certutils, websockets from netlib.exceptions import HttpException, HttpReadDisconnect, TcpTimeout, TcpDisconnect, \ TlsException from . import version, app, language, utils, log, protocols -import language.http -import language.actions -import language.exceptions -import language.websockets DEFAULT_CERT_DOMAIN = "pathod.net" @@ -116,7 +112,6 @@ class PathodHandler(tcp.BaseHandler): return None, response_log return self.handle_http_request, response_log - def handle_http_request(self, logger): """ Returns a (handler, log) tuple. @@ -358,6 +353,8 @@ class Pathod(tcp.TCPServer): staticdir=self.staticdir ) + self.loglock = threading.Lock() + def check_policy(self, req, settings): """ A policy check that verifies the request size is within limits. @@ -408,8 +405,7 @@ class Pathod(tcp.TCPServer): def add_log(self, d): if not self.noapi: - lock = threading.Lock() - with lock: + with self.loglock: d["id"] = self.logid self.log.insert(0, d) if len(self.log) > self.LOGBUF: @@ -418,17 +414,18 @@ class Pathod(tcp.TCPServer): return d["id"] def clear_log(self): - lock = threading.Lock() - with lock: + with self.loglock: self.log = [] def log_by_id(self, identifier): - for i in self.log: - if i["id"] == identifier: - return i + with self.loglock: + for i in self.log: + if i["id"] == identifier: + return i def get_log(self): - return self.log + with self.loglock: + return self.log def main(args): # pragma: no cover diff --git a/pathod/pathod_cmdline.py b/pathod/pathod_cmdline.py index 1f972a49..a4f05faf 100644 --- a/pathod/pathod_cmdline.py +++ b/pathod/pathod_cmdline.py @@ -4,7 +4,7 @@ import os import os.path import re -from netlib import tcp +from netlib import tcp, human from . import pathod, version, utils @@ -49,12 +49,12 @@ def args_pathod(argv, stdout_=sys.stdout, stderr_=sys.stderr): help=""" URL path specifying prefix for URL crafting commands. (%s) - """%pathod.DEFAULT_CRAFT_ANCHOR + """ % pathod.DEFAULT_CRAFT_ANCHOR ) parser.add_argument( "--confdir", - action="store", type = str, dest="confdir", default='~/.mitmproxy', - help = "Configuration directory. (~/.mitmproxy)" + action="store", type=str, dest="confdir", default='~/.mitmproxy', + help="Configuration directory. (~/.mitmproxy)" ) parser.add_argument( "-d", dest='staticdir', default=None, type=str, @@ -117,8 +117,8 @@ def args_pathod(argv, stdout_=sys.stdout, stderr_=sys.stderr): ) group.add_argument( "--cert", dest='ssl_certs', default=[], type=str, - metavar = "SPEC", action="append", - help = """ + metavar="SPEC", action="append", + help=""" Add an SSL certificate. SPEC is of the form "[domain=]path". The domain may include a wildcard, and is equal to "*" if not specified. The file at path is a certificate in PEM format. If a private key is included in @@ -177,7 +177,6 @@ def args_pathod(argv, stdout_=sys.stdout, stderr_=sys.stderr): help="Output all received & sent HTTP/2 frames" ) - args = parser.parse_args(argv[1:]) args.ssl_version, args.ssl_options = tcp.sslversion_choices[args.ssl_version] @@ -206,7 +205,7 @@ def args_pathod(argv, stdout_=sys.stdout, stderr_=sys.stderr): sizelimit = None if args.sizelimit: try: - sizelimit = utils.parse_size(args.sizelimit) + sizelimit = human.parse_size(args.sizelimit) except ValueError as v: return parser.error(v) args.sizelimit = sizelimit diff --git a/pathod/protocols/__init__.py b/pathod/protocols/__init__.py index 1a8c7dab..f8f3008f 100644 --- a/pathod/protocols/__init__.py +++ b/pathod/protocols/__init__.py @@ -1 +1,7 @@ from . import http, http2, websockets + +__all__ = [ + "http", + "http2", + "websockets", +] diff --git a/pathod/protocols/http.py b/pathod/protocols/http.py index 1f1765cb..d09b5bf2 100644 --- a/pathod/protocols/http.py +++ b/pathod/protocols/http.py @@ -1,6 +1,6 @@ -from netlib import tcp, wsgi -from netlib.exceptions import HttpReadDisconnect, TlsException -from netlib.http import http1, Request +from netlib import wsgi +from netlib.exceptions import TlsException +from netlib.http import http1 from .. import version, language diff --git a/pathod/protocols/http2.py b/pathod/protocols/http2.py index a098a14e..3f45ec80 100644 --- a/pathod/protocols/http2.py +++ b/pathod/protocols/http2.py @@ -1,5 +1,6 @@ from netlib.http import http2 -from .. import version, app, language, utils, log +from .. import language + class HTTP2Protocol: diff --git a/pathod/protocols/websockets.py b/pathod/protocols/websockets.py index 134d27bc..2b60e618 100644 --- a/pathod/protocols/websockets.py +++ b/pathod/protocols/websockets.py @@ -18,7 +18,7 @@ class WebsocketsProtocol: frm = websockets.Frame.from_file(self.pathod_handler.rfile) except NetlibException as e: lg("Error reading websocket frame: %s" % e) - break + return None, None ended = time.time() lg(frm.human_readable()) retlog = dict( diff --git a/pathod/test.py b/pathod/test.py index 23b7a5b6..11462729 100644 --- a/pathod/test.py +++ b/pathod/test.py @@ -1,12 +1,14 @@ from six.moves import cStringIO as StringIO import threading +import time + from six.moves import queue -import requests -import requests.packages.urllib3 from . import pathod -requests.packages.urllib3.disable_warnings() + +class TimeoutError(Exception): + pass class Daemon: @@ -39,39 +41,51 @@ class Daemon: """ return "%s/p/%s" % (self.urlbase, spec) - def info(self): - """ - Return some basic info about the remote daemon. - """ - resp = requests.get("%s/api/info" % self.urlbase, verify=False) - return resp.json() - def text_log(self): return self.logfp.getvalue() + def wait_for_silence(self, timeout=5): + start = time.time() + while 1: + if time.time() - start >= timeout: + raise TimeoutError( + "%s service threads still alive" % + self.thread.server.handler_counter.count + ) + if self.thread.server.handler_counter.count == 0: + return + + def expect_log(self, n, timeout=5): + l = [] + start = time.time() + while True: + l = self.log() + if time.time() - start >= timeout: + return None + if len(l) >= n: + break + return l + def last_log(self): """ Returns the last logged request, or None. """ - l = self.log() + l = self.expect_log(1) if not l: return None - return l[0] + return l[-1] def log(self): """ Return the log buffer as a list of dictionaries. """ - resp = requests.get("%s/api/log" % self.urlbase, verify=False) - return resp.json()["log"] + return self.thread.server.get_log() def clear_log(self): """ Clear the log. """ - self.logfp.truncate(0) - resp = requests.get("%s/api/clear_log" % self.urlbase, verify=False) - return resp.ok + return self.thread.server.clear_log() def shutdown(self): """ @@ -88,6 +102,7 @@ class _PaThread(threading.Thread): self.name = "PathodThread" self.iface, self.q, self.ssl = iface, q, ssl self.daemonargs = daemonargs + self.server = None def run(self): self.server = pathod.Pathod( diff --git a/pathod/utils.py b/pathod/utils.py index d1e2dd00..3276198a 100644 --- a/pathod/utils.py +++ b/pathod/utils.py @@ -3,15 +3,6 @@ import sys import netlib.utils -SIZE_UNITS = dict( - b=1024 ** 0, - k=1024 ** 1, - m=1024 ** 2, - g=1024 ** 3, - t=1024 ** 4, -) - - class MemBool(object): """ @@ -26,20 +17,6 @@ class MemBool(object): return bool(v) -def parse_size(s): - try: - return int(s) - except ValueError: - pass - for i in SIZE_UNITS.keys(): - if s.endswith(i): - try: - return int(s[:-1]) * SIZE_UNITS[i] - except ValueError: - break - raise ValueError("Invalid size specification.") - - def parse_anchor_spec(s): """ Return a tuple, or None on error. @@ -49,33 +26,6 @@ def parse_anchor_spec(s): return tuple(s.split("=", 1)) -def xrepr(s): - return repr(s)[1:-1] - - -def inner_repr(s): - """ - Returns the inner portion of a string or unicode repr (i.e. without the - quotes) - """ - if isinstance(s, unicode): - return repr(s)[2:-1] - else: - return repr(s)[1:-1] - - -def escape_unprintables(s): - """ - Like inner_repr, but preserves line breaks. - """ - s = s.replace("\r\n", "PATHOD_MARKER_RN") - s = s.replace("\n", "PATHOD_MARKER_N") - s = inner_repr(s) - s = s.replace("PATHOD_MARKER_RN", "\n") - s = s.replace("PATHOD_MARKER_N", "\n") - return s - - data = netlib.utils.Data(__name__) diff --git a/pathod/version.py b/pathod/version.py index 2da7637d..3441be92 100644 --- a/pathod/version.py +++ b/pathod/version.py @@ -4,3 +4,10 @@ from netlib.version import VERSION, IVERSION NAME = "pathod" NAMEVERSION = NAME + " " + VERSION + +__all__ = [ + "NAME", + "NAMEVERSION", + "VERSION", + "IVERSION", +] @@ -1,11 +1,9 @@ [flake8] -max-line-length = 120 -max-complexity = 20 - -[pep8] -max-line-length = 120 -exclude = */contrib/* -ignore = E251 +max-line-length = 140 +max-complexity = 25 +ignore = E251,C901 +exclude = mitmproxy/contrib/*,test/mitmproxy/data/* +builtins = file,open,basestring,xrange,unicode,long,cmp [pytest] testpaths = test @@ -13,8 +11,7 @@ addopts = --capture=no [coverage:run] branch = True -include = *mitmproxy*, *netlib*, *pathod* -omit = *contrib*, *tnetstring*, *platform*, *console*, *main.py +omit = *contrib*, *tnetstring*, *platform*, *main.py [coverage:report] show_missing = True @@ -65,7 +65,7 @@ setup( "certifi>=2015.11.20.1", # no semver here - this should always be on the last release! "configargparse>=0.10, <0.11", "construct>=2.5.2, <2.6", - "cryptography>=1.3,<1.4", + "cryptography>=1.3, <1.4", "Flask>=0.10.1, <0.11", "h2>=2.3.1, <3", "html2text>=2016.1.8, <=2016.4.2", @@ -75,7 +75,7 @@ setup( "passlib>=1.6.5, <1.7", "pyasn1>=0.1.9, <0.2", "pyOpenSSL>=16.0, <17.0", - "pyparsing>=2.0,<2.1", # 2.1.1 breaks our binaries, see https://sourceforge.net/p/pyparsing/bugs/93/ + "pyparsing>=2.0, <2.1", # 2.1.1 breaks our binaries, see https://sourceforge.net/p/pyparsing/bugs/93/ "pyperclip>=1.5.22, <1.6", "requests>=2.9.1, <2.10", "six>=1.10, <1.11", @@ -98,8 +98,9 @@ setup( ], 'dev': [ "coveralls>=1.1, <1.2", - "mock>=2.0,<2.1", - "pytest>=2.8.7,<2.10", + "mock>=2.0, <2.1", + "flake8>=2.5.4, <3", + "pytest>=2.8.7, <2.10", "pytest-cov>=2.2.1, <2.3", "pytest-timeout>=1.0.0, <1.1", "pytest-xdist>=1.14, <1.15", diff --git a/test/mitmproxy/console/__init__.py b/test/mitmproxy/console/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/mitmproxy/console/__init__.py diff --git a/test/mitmproxy/test_console_common.py b/test/mitmproxy/console/test_common.py index 219200e0..ac4e0209 100644 --- a/test/mitmproxy/test_console_common.py +++ b/test/mitmproxy/console/test_common.py @@ -1,13 +1,8 @@ -import os -from unittest.case import SkipTest -if os.name == "nt": - raise SkipTest("Skipped on Windows.") - - import mitmproxy.console.common as common -from . import tutils +from .. import tutils +@tutils.skip_appveyor def test_format_flow(): f = tutils.tflow(resp=True) assert common.format_flow(f, True) diff --git a/test/mitmproxy/test_console.py b/test/mitmproxy/console/test_console.py index 58a812a6..b68dd811 100644 --- a/test/mitmproxy/test_console.py +++ b/test/mitmproxy/console/test_console.py @@ -4,7 +4,7 @@ import netlib.tutils from mitmproxy import console from mitmproxy.console import common -from . import tutils +from .. import tutils class TestConsoleState: diff --git a/test/mitmproxy/test_console_help.py b/test/mitmproxy/console/test_help.py index 0589bd68..3c1cc57c 100644 --- a/test/mitmproxy/test_console_help.py +++ b/test/mitmproxy/console/test_help.py @@ -1,11 +1,8 @@ -import os -from unittest.case import SkipTest -if os.name == "nt": - raise SkipTest("Skipped on Windows.") - import mitmproxy.console.help as help +from .. import tutils +@tutils.skip_appveyor class TestHelp: def test_helptext(self): diff --git a/test/mitmproxy/test_console_palettes.py b/test/mitmproxy/console/test_palettes.py index b5d84ddd..a99730fa 100644 --- a/test/mitmproxy/test_console_palettes.py +++ b/test/mitmproxy/console/test_palettes.py @@ -1,10 +1,8 @@ -import os -from unittest.case import SkipTest -if os.name == "nt": - raise SkipTest("Skipped on Windows.") import mitmproxy.console.palettes as palettes +from .. import tutils +@tutils.skip_appveyor class TestPalette: def test_helptext(self): diff --git a/test/mitmproxy/test_console_pathedit.py b/test/mitmproxy/console/test_pathedit.py index e2c27b7c..107a48ac 100644 --- a/test/mitmproxy/test_console_pathedit.py +++ b/test/mitmproxy/console/test_pathedit.py @@ -2,7 +2,7 @@ import os from os.path import normpath from mitmproxy.console import pathedit -from . import tutils +from .. import tutils class TestPathCompleter: diff --git a/test/mitmproxy/data/har_extractor.har b/test/mitmproxy/data/har_extractor.har index 2f5099b3..6b5e2994 100644 --- a/test/mitmproxy/data/har_extractor.har +++ b/test/mitmproxy/data/har_extractor.har @@ -58,12 +58,12 @@ }, "headers": [ { - "name": "content-length", - "value": "7" - }, - { "name": "header-response", "value": "svalue" + }, + { + "name": "content-length", + "value": "7" } ], "headersSize": 44, @@ -75,4 +75,4 @@ ] } } -}
\ No newline at end of file +} diff --git a/test/mitmproxy/scripts/a.py b/test/mitmproxy/data/scripts/a.py index d4272ac8..d4272ac8 100644 --- a/test/mitmproxy/scripts/a.py +++ b/test/mitmproxy/data/scripts/a.py diff --git a/test/mitmproxy/scripts/a_helper.py b/test/mitmproxy/data/scripts/a_helper.py index e1f1c649..e1f1c649 100644 --- a/test/mitmproxy/scripts/a_helper.py +++ b/test/mitmproxy/data/scripts/a_helper.py diff --git a/test/mitmproxy/scripts/all.py b/test/mitmproxy/data/scripts/all.py index dad2aade..dad2aade 100644 --- a/test/mitmproxy/scripts/all.py +++ b/test/mitmproxy/data/scripts/all.py diff --git a/test/mitmproxy/scripts/concurrent_decorator.py b/test/mitmproxy/data/scripts/concurrent_decorator.py index cf3ab512..e017f605 100644 --- a/test/mitmproxy/scripts/concurrent_decorator.py +++ b/test/mitmproxy/data/scripts/concurrent_decorator.py @@ -1,6 +1,7 @@ import time from mitmproxy.script import concurrent + @concurrent def request(context, flow): time.sleep(0.1) diff --git a/test/mitmproxy/scripts/concurrent_decorator_err.py b/test/mitmproxy/data/scripts/concurrent_decorator_err.py index 071b8889..071b8889 100644 --- a/test/mitmproxy/scripts/concurrent_decorator_err.py +++ b/test/mitmproxy/data/scripts/concurrent_decorator_err.py diff --git a/test/mitmproxy/scripts/duplicate_flow.py b/test/mitmproxy/data/scripts/duplicate_flow.py index e13af786..e13af786 100644 --- a/test/mitmproxy/scripts/duplicate_flow.py +++ b/test/mitmproxy/data/scripts/duplicate_flow.py diff --git a/test/mitmproxy/scripts/loaderr.py b/test/mitmproxy/data/scripts/loaderr.py index 8dc4d56d..8dc4d56d 100644 --- a/test/mitmproxy/scripts/loaderr.py +++ b/test/mitmproxy/data/scripts/loaderr.py diff --git a/test/mitmproxy/scripts/reqerr.py b/test/mitmproxy/data/scripts/reqerr.py index e7c503a8..e7c503a8 100644 --- a/test/mitmproxy/scripts/reqerr.py +++ b/test/mitmproxy/data/scripts/reqerr.py diff --git a/test/mitmproxy/scripts/starterr.py b/test/mitmproxy/data/scripts/starterr.py index b217bdfe..b217bdfe 100644 --- a/test/mitmproxy/scripts/starterr.py +++ b/test/mitmproxy/data/scripts/starterr.py diff --git a/test/mitmproxy/scripts/stream_modify.py b/test/mitmproxy/data/scripts/stream_modify.py index e26d83f1..e26d83f1 100644 --- a/test/mitmproxy/scripts/stream_modify.py +++ b/test/mitmproxy/data/scripts/stream_modify.py diff --git a/test/mitmproxy/scripts/syntaxerr.py b/test/mitmproxy/data/scripts/syntaxerr.py index 219d6b84..219d6b84 100644 --- a/test/mitmproxy/scripts/syntaxerr.py +++ b/test/mitmproxy/data/scripts/syntaxerr.py diff --git a/test/mitmproxy/scripts/tcp_stream_modify.py b/test/mitmproxy/data/scripts/tcp_stream_modify.py index d7953ef9..d7953ef9 100644 --- a/test/mitmproxy/scripts/tcp_stream_modify.py +++ b/test/mitmproxy/data/scripts/tcp_stream_modify.py diff --git a/test/mitmproxy/scripts/unloaderr.py b/test/mitmproxy/data/scripts/unloaderr.py index fba02734..fba02734 100644 --- a/test/mitmproxy/scripts/unloaderr.py +++ b/test/mitmproxy/data/scripts/unloaderr.py diff --git a/test/mitmproxy/test_flow_export/locust_get.py b/test/mitmproxy/data/test_flow_export/locust_get.py index 632d5d53..632d5d53 100644 --- a/test/mitmproxy/test_flow_export/locust_get.py +++ b/test/mitmproxy/data/test_flow_export/locust_get.py diff --git a/test/mitmproxy/test_flow_export/locust_patch.py b/test/mitmproxy/data/test_flow_export/locust_patch.py index f64e0857..f64e0857 100644 --- a/test/mitmproxy/test_flow_export/locust_patch.py +++ b/test/mitmproxy/data/test_flow_export/locust_patch.py diff --git a/test/mitmproxy/test_flow_export/locust_post.py b/test/mitmproxy/data/test_flow_export/locust_post.py index df23476a..df23476a 100644 --- a/test/mitmproxy/test_flow_export/locust_post.py +++ b/test/mitmproxy/data/test_flow_export/locust_post.py diff --git a/test/mitmproxy/test_flow_export/locust_task_get.py b/test/mitmproxy/data/test_flow_export/locust_task_get.py index 03821cd8..03821cd8 100644 --- a/test/mitmproxy/test_flow_export/locust_task_get.py +++ b/test/mitmproxy/data/test_flow_export/locust_task_get.py diff --git a/test/mitmproxy/test_flow_export/locust_task_patch.py b/test/mitmproxy/data/test_flow_export/locust_task_patch.py index d425209c..d425209c 100644 --- a/test/mitmproxy/test_flow_export/locust_task_patch.py +++ b/test/mitmproxy/data/test_flow_export/locust_task_patch.py diff --git a/test/mitmproxy/test_flow_export/locust_task_post.py b/test/mitmproxy/data/test_flow_export/locust_task_post.py index 989df455..989df455 100644 --- a/test/mitmproxy/test_flow_export/locust_task_post.py +++ b/test/mitmproxy/data/test_flow_export/locust_task_post.py diff --git a/test/mitmproxy/test_flow_export/python_get.py b/test/mitmproxy/data/test_flow_export/python_get.py index af8f7c81..af8f7c81 100644 --- a/test/mitmproxy/test_flow_export/python_get.py +++ b/test/mitmproxy/data/test_flow_export/python_get.py diff --git a/test/mitmproxy/test_flow_export/python_patch.py b/test/mitmproxy/data/test_flow_export/python_patch.py index 159e802f..159e802f 100644 --- a/test/mitmproxy/test_flow_export/python_patch.py +++ b/test/mitmproxy/data/test_flow_export/python_patch.py diff --git a/test/mitmproxy/test_flow_export/python_post.py b/test/mitmproxy/data/test_flow_export/python_post.py index b13f6441..b13f6441 100644 --- a/test/mitmproxy/test_flow_export/python_post.py +++ b/test/mitmproxy/data/test_flow_export/python_post.py diff --git a/test/mitmproxy/test_flow_export/python_post_json.py b/test/mitmproxy/data/test_flow_export/python_post_json.py index 7e105bf6..6c1b9740 100644 --- a/test/mitmproxy/test_flow_export/python_post_json.py +++ b/test/mitmproxy/data/test_flow_export/python_post_json.py @@ -6,11 +6,13 @@ headers = { 'content-type': 'application/json', } + json = { - "name": "example", - "email": "example@example.com" + u'email': u'example@example.com', + u'name': u'example', } + response = requests.request( method='POST', url=url, diff --git a/test/mitmproxy/script/test_concurrent.py b/test/mitmproxy/script/test_concurrent.py index a9c9e153..c2f169ad 100644 --- a/test/mitmproxy/script/test_concurrent.py +++ b/test/mitmproxy/script/test_concurrent.py @@ -11,7 +11,7 @@ class Dummy: @tutils.skip_appveyor def test_concurrent(): - with Script(tutils.test_data.path("scripts/concurrent_decorator.py"), None) as s: + with Script(tutils.test_data.path("data/scripts/concurrent_decorator.py"), None) as s: def reply(): reply.acked.set() reply.acked = Event() @@ -27,6 +27,6 @@ def test_concurrent(): def test_concurrent_err(): - s = Script(tutils.test_data.path("scripts/concurrent_decorator_err.py"), None) + s = Script(tutils.test_data.path("data/scripts/concurrent_decorator_err.py"), None) with tutils.raises("Concurrent decorator not supported for 'start' method"): s.load() diff --git a/test/mitmproxy/script/test_script.py b/test/mitmproxy/script/test_script.py index a9b55977..fe98fab5 100644 --- a/test/mitmproxy/script/test_script.py +++ b/test/mitmproxy/script/test_script.py @@ -21,19 +21,19 @@ class TestParseCommand: def test_parse_args(self): with tutils.chdir(tutils.test_data.dirname): - assert Script.parse_command("scripts/a.py") == ["scripts/a.py"] - assert Script.parse_command("scripts/a.py foo bar") == ["scripts/a.py", "foo", "bar"] - assert Script.parse_command("scripts/a.py 'foo bar'") == ["scripts/a.py", "foo bar"] + assert Script.parse_command("data/scripts/a.py") == ["data/scripts/a.py"] + assert Script.parse_command("data/scripts/a.py foo bar") == ["data/scripts/a.py", "foo", "bar"] + assert Script.parse_command("data/scripts/a.py 'foo bar'") == ["data/scripts/a.py", "foo bar"] @tutils.skip_not_windows def test_parse_windows(self): with tutils.chdir(tutils.test_data.dirname): - assert Script.parse_command("scripts\\a.py") == ["scripts\\a.py"] - assert Script.parse_command("scripts\\a.py 'foo \\ bar'") == ["scripts\\a.py", 'foo \\ bar'] + assert Script.parse_command("data\\scripts\\a.py") == ["data\\scripts\\a.py"] + assert Script.parse_command("data\\scripts\\a.py 'foo \\ bar'") == ["data\\scripts\\a.py", 'foo \\ bar'] def test_simple(): - with tutils.chdir(tutils.test_data.path("scripts")): + with tutils.chdir(tutils.test_data.path("data/scripts")): s = Script("a.py --var 42", None) assert s.filename == "a.py" assert s.ns is None @@ -55,7 +55,7 @@ def test_simple(): def test_script_exception(): - with tutils.chdir(tutils.test_data.path("scripts")): + with tutils.chdir(tutils.test_data.path("data/scripts")): s = Script("syntaxerr.py", None) with tutils.raises(ScriptException): s.load() diff --git a/test/mitmproxy/test_app.py b/test/mitmproxy/test_app.py index 8d8ce271..4c9eff08 100644 --- a/test/mitmproxy/test_app.py +++ b/test/mitmproxy/test_app.py @@ -1,4 +1,4 @@ -from . import tutils, tservers +from . import tservers class TestApp(tservers.HTTPProxyTest): @@ -7,8 +7,7 @@ class TestApp(tservers.HTTPProxyTest): assert self.app("/").status_code == 200 def test_cert(self): - with tutils.tmpdir() as d: - for ext in ["pem", "p12"]: - resp = self.app("/cert/%s" % ext) - assert resp.status_code == 200 - assert resp.content + for ext in ["pem", "p12"]: + resp = self.app("/cert/%s" % ext) + assert resp.status_code == 200 + assert resp.content diff --git a/test/mitmproxy/test_contentview.py b/test/mitmproxy/test_contentview.py index c00afa5f..9142bdad 100644 --- a/test/mitmproxy/test_contentview.py +++ b/test/mitmproxy/test_contentview.py @@ -1,8 +1,8 @@ from mitmproxy.exceptions import ContentViewException from netlib.http import Headers from netlib.odict import ODict -import netlib.utils from netlib import encoding +from netlib.http import url import mitmproxy.contentviews as cv from . import tutils @@ -60,10 +60,10 @@ class TestContentView: assert f[0] == "Query" def test_view_urlencoded(self): - d = netlib.utils.urlencode([("one", "two"), ("three", "four")]) + d = url.encode([("one", "two"), ("three", "four")]) v = cv.ViewURLEncoded() assert v(d) - d = netlib.utils.urlencode([("adsfa", "")]) + d = url.encode([("adsfa", "")]) v = cv.ViewURLEncoded() assert v(d) diff --git a/test/mitmproxy/test_controller.py b/test/mitmproxy/test_controller.py index f7bf615a..83ad428e 100644 --- a/test/mitmproxy/test_controller.py +++ b/test/mitmproxy/test_controller.py @@ -2,7 +2,7 @@ from threading import Thread, Event from mock import Mock -from mitmproxy.controller import Reply, DummyReply, Channel, ServerThread, ServerMaster, Master +from mitmproxy import controller from six.moves import queue from mitmproxy.exceptions import Kill @@ -10,11 +10,15 @@ from mitmproxy.proxy import DummyServer from netlib.tutils import raises +class TMsg: + pass + + class TestMaster(object): def test_simple(self): - - class DummyMaster(Master): - def handle_panic(self, _): + class DummyMaster(controller.Master): + @controller.handler + def log(self, _): m.should_exit.set() def tick(self, timeout): @@ -23,14 +27,14 @@ class TestMaster(object): m = DummyMaster() assert not m.should_exit.is_set() - m.event_queue.put(("panic", 42)) + msg = TMsg() + msg.reply = controller.DummyReply() + m.event_queue.put(("log", msg)) m.run() assert m.should_exit.is_set() - -class TestServerMaster(object): - def test_simple(self): - m = ServerMaster() + def test_server_simple(self): + m = controller.Master() s = DummyServer(None) m.add_server(s) m.start() @@ -42,7 +46,7 @@ class TestServerMaster(object): class TestServerThread(object): def test_simple(self): m = Mock() - t = ServerThread(m) + t = controller.ServerThread(m) t.run() assert m.serve_forever.called @@ -50,7 +54,7 @@ class TestServerThread(object): class TestChannel(object): def test_tell(self): q = queue.Queue() - channel = Channel(q, Event()) + channel = controller.Channel(q, Event()) m = Mock() channel.tell("test", m) assert q.get() == ("test", m) @@ -66,21 +70,21 @@ class TestChannel(object): Thread(target=reply).start() - channel = Channel(q, Event()) + channel = controller.Channel(q, Event()) assert channel.ask("test", Mock()) == 42 def test_ask_shutdown(self): q = queue.Queue() done = Event() done.set() - channel = Channel(q, done) + channel = controller.Channel(q, done) with raises(Kill): channel.ask("test", Mock()) class TestDummyReply(object): def test_simple(self): - reply = DummyReply() + reply = controller.DummyReply() assert not reply.acked reply() assert reply.acked @@ -88,18 +92,18 @@ class TestDummyReply(object): class TestReply(object): def test_simple(self): - reply = Reply(42) + reply = controller.Reply(42) assert not reply.acked reply("foo") assert reply.acked assert reply.q.get() == "foo" def test_default(self): - reply = Reply(42) + reply = controller.Reply(42) reply() assert reply.q.get() == 42 def test_reply_none(self): - reply = Reply(42) + reply = controller.Reply(42) reply(None) assert reply.q.get() is None diff --git a/test/mitmproxy/test_dump.py b/test/mitmproxy/test_dump.py index ad4cee53..36b78168 100644 --- a/test/mitmproxy/test_dump.py +++ b/test/mitmproxy/test_dump.py @@ -64,14 +64,14 @@ class TestDumpMaster: f = tutils.tflow(req=netlib.tutils.treq(content=content)) l = Log("connect") l.reply = mock.MagicMock() - m.handle_log(l) - m.handle_clientconnect(f.client_conn) - m.handle_serverconnect(f.server_conn) - m.handle_request(f) + m.log(l) + m.clientconnect(f.client_conn) + m.serverconnect(f.server_conn) + m.request(f) if not f.error: f.response = HTTPResponse.wrap(netlib.tutils.tresp(content=content)) - f = m.handle_response(f) - m.handle_clientdisconnect(f.client_conn) + f = m.response(f) + m.clientdisconnect(f.client_conn) return f def _dummy_cycle(self, n, filt, content, **options): @@ -95,8 +95,8 @@ class TestDumpMaster: o = dump.Options(flow_detail=1) m = dump.DumpMaster(None, o, outfile=cs) f = tutils.tflow(err=True) - m.handle_request(f) - assert m.handle_error(f) + m.request(f) + assert m.error(f) assert "error" in cs.getvalue() def test_missing_content(self): @@ -105,10 +105,10 @@ class TestDumpMaster: m = dump.DumpMaster(None, o, outfile=cs) f = tutils.tflow() f.request.content = None - m.handle_request(f) + m.request(f) f.response = HTTPResponse.wrap(netlib.tutils.tresp()) f.response.content = None - m.handle_response(f) + m.response(f) assert "content missing" in cs.getvalue() def test_replay(self): @@ -160,7 +160,7 @@ class TestDumpMaster: assert o.verbosity == 2 def test_filter(self): - assert not "GET" in self._dummy_cycle(1, "~u foo", "", verbosity=1) + assert "GET" not in self._dummy_cycle(1, "~u foo", "", verbosity=1) def test_app(self): o = dump.Options(app=True) @@ -218,7 +218,7 @@ class TestDumpMaster: def test_script(self): ret = self._dummy_cycle( 1, None, "", - scripts=[tutils.test_data.path("scripts/all.py")], verbosity=1 + scripts=[tutils.test_data.path("data/scripts/all.py")], verbosity=1 ) assert "XCLIENTCONNECT" in ret assert "XSERVERCONNECT" in ret diff --git a/test/mitmproxy/test_examples.py b/test/mitmproxy/test_examples.py index c4b06f4b..607d6faf 100644 --- a/test/mitmproxy/test_examples.py +++ b/test/mitmproxy/test_examples.py @@ -3,7 +3,7 @@ import json import os from contextlib import contextmanager -from mitmproxy import utils, script +from mitmproxy import script from mitmproxy.proxy import config import netlib.utils from netlib import tutils as netutils diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py index 3e78a5c4..1b1f03f9 100644 --- a/test/mitmproxy/test_flow.py +++ b/test/mitmproxy/test_flow.py @@ -4,7 +4,6 @@ from six.moves import cStringIO as StringIO import mock import netlib.utils -from netlib import odict from netlib.http import Headers from mitmproxy import filt, controller, tnetstring, flow from mitmproxy.exceptions import FlowReadException, ScriptException @@ -54,8 +53,8 @@ class TestStickyCookieState: assert s.domain_match("www.google.com", ".google.com") assert s.domain_match("google.com", ".google.com") - def test_handle_response(self): - c = "SSID=mooo; domain=.google.com, FOO=bar; Domain=.google.com; Path=/; "\ + def test_response(self): + c = "SSID=mooo; domain=.google.com, FOO=bar; Domain=.google.com; Path=/; " \ "Expires=Wed, 13-Jan-2021 22:23:01 GMT; Secure; " s, f = self._response(c, "host") @@ -101,7 +100,7 @@ class TestStickyCookieState: assert len(s.jar[googlekey].keys()) == 1 assert s.jar[googlekey]["somecookie"].items()[0][1] == "newvalue" - def test_handle_request(self): + def test_request(self): s, f = self._response("SSID=mooo", "www.google.com") assert "cookie" not in f.request.headers s.handle_request(f) @@ -110,7 +109,7 @@ class TestStickyCookieState: class TestStickyAuthState: - def test_handle_response(self): + def test_response(self): s = flow.StickyAuthState(filt.parse(".*")) f = tutils.tflow(resp=True) f.request.headers["authorization"] = "foo" @@ -389,22 +388,22 @@ class TestFlow(object): del b["id"] assert a == b assert not f == f2 - assert not f is f2 + assert f is not f2 assert f.request.get_state() == f2.request.get_state() - assert not f.request is f2.request + assert f.request is not f2.request assert f.request.headers == f2.request.headers - assert not f.request.headers is f2.request.headers + assert f.request.headers is not f2.request.headers assert f.response.get_state() == f2.response.get_state() - assert not f.response is f2.response + assert f.response is not f2.response f = tutils.tflow(err=True) f2 = f.copy() - assert not f is f2 - assert not f.request is f2.request + assert f is not f2 + assert f.request is not f2.request assert f.request.headers == f2.request.headers - assert not f.request.headers is f2.request.headers + assert f.request.headers is not f2.request.headers assert f.error.get_state() == f2.error.get_state() - assert not f.error is f2.error + assert f.error is not f2.error def test_match(self): f = tutils.tflow(resp=True) @@ -461,25 +460,20 @@ class TestFlow(object): fm = flow.FlowMaster(None, s) f = tutils.tflow() f.intercept(mock.Mock()) - assert not f.reply.acked f.kill(fm) - assert f.reply.acked + for i in s.view: + assert "killed" in str(i.error) def test_killall(self): s = flow.State() fm = flow.FlowMaster(None, s) f = tutils.tflow() - fm.handle_request(f) - - f = tutils.tflow() - fm.handle_request(f) + f.intercept(fm) - for i in s.view: - assert not i.reply.acked s.killall(fm) for i in s.view: - assert i.reply.acked + assert "killed" in str(i.error) def test_accept_intercept(self): f = tutils.tflow() @@ -767,13 +761,13 @@ class TestFlowMaster: s = flow.State() fm = flow.FlowMaster(None, s) - fm.load_script(tutils.test_data.path("scripts/a.py")) - fm.load_script(tutils.test_data.path("scripts/a.py")) + fm.load_script(tutils.test_data.path("data/scripts/a.py")) + fm.load_script(tutils.test_data.path("data/scripts/a.py")) fm.unload_scripts() with tutils.raises(ScriptException): fm.load_script("nonexistent") try: - fm.load_script(tutils.test_data.path("scripts/starterr.py")) + fm.load_script(tutils.test_data.path("data/scripts/starterr.py")) except ScriptException as e: assert "ValueError" in str(e) assert len(fm.scripts) == 0 @@ -802,39 +796,39 @@ class TestFlowMaster: def test_script_reqerr(self): s = flow.State() fm = flow.FlowMaster(None, s) - fm.load_script(tutils.test_data.path("scripts/reqerr.py")) + fm.load_script(tutils.test_data.path("data/scripts/reqerr.py")) f = tutils.tflow() - fm.handle_clientconnect(f.client_conn) - assert fm.handle_request(f) + fm.clientconnect(f.client_conn) + assert fm.request(f) def test_script(self): s = flow.State() fm = flow.FlowMaster(None, s) - fm.load_script(tutils.test_data.path("scripts/all.py")) + fm.load_script(tutils.test_data.path("data/scripts/all.py")) f = tutils.tflow(resp=True) - fm.handle_clientconnect(f.client_conn) + fm.clientconnect(f.client_conn) assert fm.scripts[0].ns["log"][-1] == "clientconnect" - fm.handle_serverconnect(f.server_conn) + fm.serverconnect(f.server_conn) assert fm.scripts[0].ns["log"][-1] == "serverconnect" - fm.handle_request(f) + fm.request(f) assert fm.scripts[0].ns["log"][-1] == "request" - fm.handle_response(f) + fm.response(f) assert fm.scripts[0].ns["log"][-1] == "response" # load second script - fm.load_script(tutils.test_data.path("scripts/all.py")) + fm.load_script(tutils.test_data.path("data/scripts/all.py")) assert len(fm.scripts) == 2 - fm.handle_clientdisconnect(f.server_conn) + fm.clientdisconnect(f.server_conn) assert fm.scripts[0].ns["log"][-1] == "clientdisconnect" assert fm.scripts[1].ns["log"][-1] == "clientdisconnect" # unload first script fm.unload_scripts() assert len(fm.scripts) == 0 - fm.load_script(tutils.test_data.path("scripts/all.py")) + fm.load_script(tutils.test_data.path("data/scripts/all.py")) f.error = tutils.terr() - fm.handle_error(f) + fm.error(f) assert fm.scripts[0].ns["log"][-1] == "error" def test_duplicate_flow(self): @@ -859,23 +853,22 @@ class TestFlowMaster: fm.anticache = True fm.anticomp = True f = tutils.tflow(req=None) - fm.handle_clientconnect(f.client_conn) + fm.clientconnect(f.client_conn) f.request = HTTPRequest.wrap(netlib.tutils.treq()) - fm.handle_request(f) + fm.request(f) assert s.flow_count() == 1 f.response = HTTPResponse.wrap(netlib.tutils.tresp()) - fm.handle_response(f) - assert not fm.handle_response(None) + fm.response(f) assert s.flow_count() == 1 - fm.handle_clientdisconnect(f.client_conn) + fm.clientdisconnect(f.client_conn) f.error = Error("msg") f.error.reply = controller.DummyReply() - fm.handle_error(f) + fm.error(f) - fm.load_script(tutils.test_data.path("scripts/a.py")) + fm.load_script(tutils.test_data.path("data/scripts/a.py")) fm.shutdown() def test_client_playback(self): @@ -902,7 +895,7 @@ class TestFlowMaster: assert fm.state.flow_count() f.error = Error("error") - fm.handle_error(f) + fm.error(f) def test_server_playback(self): s = flow.State() @@ -983,12 +976,12 @@ class TestFlowMaster: fm.set_stickycookie(".*") f = tutils.tflow(resp=True) f.response.headers["set-cookie"] = "foo=bar" - fm.handle_request(f) - fm.handle_response(f) + fm.request(f) + fm.response(f) assert fm.stickycookie_state.jar - assert not "cookie" in f.request.headers + assert "cookie" not in f.request.headers f = f.copy() - fm.handle_request(f) + fm.request(f) assert f.request.headers["cookie"] == "foo=bar" def test_stickyauth(self): @@ -1003,12 +996,12 @@ class TestFlowMaster: fm.set_stickyauth(".*") f = tutils.tflow(resp=True) f.request.headers["authorization"] = "foo" - fm.handle_request(f) + fm.request(f) f = tutils.tflow(resp=True) assert fm.stickyauth_state.hosts - assert not "authorization" in f.request.headers - fm.handle_request(f) + assert "authorization" not in f.request.headers + fm.request(f) assert f.request.headers["authorization"] == "foo" def test_stream(self): @@ -1024,15 +1017,15 @@ class TestFlowMaster: f = tutils.tflow(resp=True) fm.start_stream(file(p, "ab"), None) - fm.handle_request(f) - fm.handle_response(f) + fm.request(f) + fm.response(f) fm.stop_stream() assert r()[0].response f = tutils.tflow() fm.start_stream(file(p, "ab"), None) - fm.handle_request(f) + fm.request(f) fm.shutdown() assert not r()[1].response @@ -1077,8 +1070,8 @@ class TestRequest: r.headers["if-modified-since"] = "test" r.headers["if-none-match"] = "test" r.anticache() - assert not "if-modified-since" in r.headers - assert not "if-none-match" in r.headers + assert "if-modified-since" not in r.headers + assert "if-none-match" not in r.headers def test_replace(self): r = HTTPRequest.wrap(netlib.tutils.treq()) @@ -1087,7 +1080,7 @@ class TestRequest: r.content = "afoob" assert r.replace("foo(?i)", "boo") == 4 assert r.path == "path/boo" - assert not "foo" in r.content + assert "foo" not in r.content assert r.headers["boo"] == "boo" def test_constrain_encoding(self): @@ -1129,7 +1122,7 @@ class TestResponse: r.headers["Foo"] = "fOo" r.content = "afoob" assert r.replace("foo(?i)", "boo") == 3 - assert not "foo" in r.content + assert "foo" not in r.content assert r.headers["boo"] == "boo" def test_get_content_type(self): @@ -1161,11 +1154,9 @@ class TestError: class TestClientConnection: - def test_state(self): - c = tutils.tclient_conn() - assert ClientConnection.from_state(c.get_state()).get_state() ==\ + assert ClientConnection.from_state(c.get_state()).get_state() == \ c.get_state() c2 = tutils.tclient_conn() diff --git a/test/mitmproxy/test_flow_export.py b/test/mitmproxy/test_flow_export.py index c252c5bd..9a263b1b 100644 --- a/test/mitmproxy/test_flow_export.py +++ b/test/mitmproxy/test_flow_export.py @@ -1,10 +1,9 @@ -import json from textwrap import dedent import re import netlib.tutils from netlib.http import Headers -from mitmproxy import flow_export +from mitmproxy.flow import export # heh from . import tutils @@ -21,49 +20,54 @@ def python_equals(testdata, text): assert clean_blanks(text).rstrip() == clean_blanks(d).rstrip() -req_get = lambda: netlib.tutils.treq(method='GET', content='', path=b"/path?a=foo&a=bar&b=baz") +def req_get(): + return netlib.tutils.treq(method='GET', content='', path=b"/path?a=foo&a=bar&b=baz") -req_post = lambda: netlib.tutils.treq(method='POST', headers=None) -req_patch = lambda: netlib.tutils.treq(method='PATCH', path=b"/path?query=param") +def req_post(): + return netlib.tutils.treq(method='POST', headers=()) + + +def req_patch(): + return netlib.tutils.treq(method='PATCH', path=b"/path?query=param") class TestExportCurlCommand(): def test_get(self): flow = tutils.tflow(req=req_get()) result = """curl -H 'header:qvalue' -H 'content-length:7' 'http://address/path?a=foo&a=bar&b=baz'""" - assert flow_export.curl_command(flow) == result + assert export.curl_command(flow) == result def test_post(self): flow = tutils.tflow(req=req_post()) result = """curl -X POST 'http://address/path' --data-binary 'content'""" - assert flow_export.curl_command(flow) == result + assert export.curl_command(flow) == result def test_patch(self): flow = tutils.tflow(req=req_patch()) result = """curl -H 'header:qvalue' -H 'content-length:7' -X PATCH 'http://address/path?query=param' --data-binary 'content'""" - assert flow_export.curl_command(flow) == result + assert export.curl_command(flow) == result class TestExportPythonCode(): def test_get(self): flow = tutils.tflow(req=req_get()) - python_equals("test_flow_export/python_get.py", flow_export.python_code(flow)) + python_equals("data/test_flow_export/python_get.py", export.python_code(flow)) def test_post(self): flow = tutils.tflow(req=req_post()) - python_equals("test_flow_export/python_post.py", flow_export.python_code(flow)) + python_equals("data/test_flow_export/python_post.py", export.python_code(flow)) def test_post_json(self): p = req_post() p.content = '{"name": "example", "email": "example@example.com"}' p.headers = Headers(content_type="application/json") flow = tutils.tflow(req=p) - python_equals("test_flow_export/python_post_json.py", flow_export.python_code(flow)) + python_equals("data/test_flow_export/python_post_json.py", export.python_code(flow)) def test_patch(self): flow = tutils.tflow(req=req_patch()) - python_equals("test_flow_export/python_patch.py", flow_export.python_code(flow)) + python_equals("data/test_flow_export/python_patch.py", export.python_code(flow)) class TestRawRequest(): @@ -76,7 +80,7 @@ class TestRawRequest(): host: address:22\r \r """).strip(" ").lstrip() - assert flow_export.raw_request(flow) == result + assert export.raw_request(flow) == result def test_post(self): flow = tutils.tflow(req=req_post()) @@ -86,7 +90,7 @@ class TestRawRequest(): \r content """).strip() - assert flow_export.raw_request(flow) == result + assert export.raw_request(flow) == result def test_patch(self): flow = tutils.tflow(req=req_patch()) @@ -98,54 +102,54 @@ class TestRawRequest(): \r content """).strip() - assert flow_export.raw_request(flow) == result + assert export.raw_request(flow) == result class TestExportLocustCode(): def test_get(self): flow = tutils.tflow(req=req_get()) - python_equals("test_flow_export/locust_get.py", flow_export.locust_code(flow)) + python_equals("data/test_flow_export/locust_get.py", export.locust_code(flow)) def test_post(self): p = req_post() p.content = '''content''' p.headers = '' flow = tutils.tflow(req=p) - python_equals("test_flow_export/locust_post.py", flow_export.locust_code(flow)) + python_equals("data/test_flow_export/locust_post.py", export.locust_code(flow)) def test_patch(self): flow = tutils.tflow(req=req_patch()) - python_equals("test_flow_export/locust_patch.py", flow_export.locust_code(flow)) + python_equals("data/test_flow_export/locust_patch.py", export.locust_code(flow)) class TestExportLocustTask(): def test_get(self): flow = tutils.tflow(req=req_get()) - python_equals("test_flow_export/locust_task_get.py", flow_export.locust_task(flow)) + python_equals("data/test_flow_export/locust_task_get.py", export.locust_task(flow)) def test_post(self): flow = tutils.tflow(req=req_post()) - python_equals("test_flow_export/locust_task_post.py", flow_export.locust_task(flow)) + python_equals("data/test_flow_export/locust_task_post.py", export.locust_task(flow)) def test_patch(self): flow = tutils.tflow(req=req_patch()) - python_equals("test_flow_export/locust_task_patch.py", flow_export.locust_task(flow)) + python_equals("data/test_flow_export/locust_task_patch.py", export.locust_task(flow)) class TestIsJson(): def test_empty(self): - assert flow_export.is_json(None, None) is False + assert export.is_json(None, None) is False def test_json_type(self): headers = Headers(content_type="application/json") - assert flow_export.is_json(headers, "foobar") is False + assert export.is_json(headers, "foobar") is False def test_valid(self): headers = Headers(content_type="application/foobar") - j = flow_export.is_json(headers, '{"name": "example", "email": "example@example.com"}') + j = export.is_json(headers, '{"name": "example", "email": "example@example.com"}') assert j is False - def test_valid(self): + def test_valid2(self): headers = Headers(content_type="application/json") - j = flow_export.is_json(headers, '{"name": "example", "email": "example@example.com"}') + j = export.is_json(headers, '{"name": "example", "email": "example@example.com"}') assert isinstance(j, dict) diff --git a/test/mitmproxy/test_flow_format_compat.py b/test/mitmproxy/test_flow_format_compat.py index 2c477cc2..b2cef88d 100644 --- a/test/mitmproxy/test_flow_format_compat.py +++ b/test/mitmproxy/test_flow_format_compat.py @@ -1,4 +1,5 @@ -from mitmproxy.flow import FlowReader, FlowReadException +from mitmproxy.flow import FlowReader +from mitmproxy.exceptions import FlowReadException from . import tutils diff --git a/test/mitmproxy/test_protocol_http2.py b/test/mitmproxy/test_protocol_http2.py index c3950975..23072260 100644 --- a/test/mitmproxy/test_protocol_http2.py +++ b/test/mitmproxy/test_protocol_http2.py @@ -2,15 +2,21 @@ from __future__ import (absolute_import, print_function, division) -import OpenSSL import pytest import traceback import os import tempfile +import h2 from mitmproxy.proxy.config import ProxyConfig from mitmproxy.cmdline import APP_HOST, APP_PORT +import netlib +from ..netlib import tservers as netlib_tservers +from netlib.http.http2 import framereader + +from . import tservers + import logging logging.getLogger("hyper.packages.hpack.hpack").setLevel(logging.WARNING) logging.getLogger("requests.packages.urllib3.connectionpool").setLevel(logging.WARNING) @@ -19,13 +25,6 @@ logging.getLogger("passlib.registry").setLevel(logging.WARNING) logging.getLogger("PIL.Image").setLevel(logging.WARNING) logging.getLogger("PIL.PngImagePlugin").setLevel(logging.WARNING) -import netlib -from ..netlib import tservers as netlib_tservers -from netlib.utils import http2_read_raw_frame - -import h2 - -from . import tservers requires_alpn = pytest.mark.skipif( not netlib.tcp.HAS_ALPN, @@ -49,7 +48,7 @@ class _Http2ServerBase(netlib_tservers.ServerTestBase): done = False while not done: try: - raw = b''.join(http2_read_raw_frame(self.rfile)) + raw = b''.join(framereader.http2_read_raw_frame(self.rfile)) events = h2_conn.receive_data(raw) except: break @@ -165,12 +164,21 @@ class TestSimple(_Http2TestBase, _Http2ServerBase): assert ('client-foo', 'client-bar-1') in event.headers assert ('client-foo', 'client-bar-2') in event.headers - h2_conn.send_headers(event.stream_id, [ - (':status', '200'), - ('server-foo', 'server-bar'), - ('föo', 'bär'), - ('X-Stream-ID', str(event.stream_id)), - ]) + import warnings + with warnings.catch_warnings(): + # Ignore UnicodeWarning: + # h2/utilities.py:64: UnicodeWarning: Unicode equal comparison + # failed to convert both arguments to Unicode - interpreting + # them as being unequal. + # elif header[0] in (b'cookie', u'cookie') and len(header[1]) < 20: + + warnings.simplefilter("ignore") + h2_conn.send_headers(event.stream_id, [ + (':status', '200'), + ('server-foo', 'server-bar'), + ('föo', 'bär'), + ('X-Stream-ID', str(event.stream_id)), + ]) h2_conn.send_data(event.stream_id, b'foobar') h2_conn.end_stream(event.stream_id) wfile.write(h2_conn.data_to_send()) @@ -192,7 +200,7 @@ class TestSimple(_Http2TestBase, _Http2ServerBase): done = False while not done: try: - events = h2_conn.receive_data(b''.join(http2_read_raw_frame(client.rfile))) + events = h2_conn.receive_data(b''.join(framereader.http2_read_raw_frame(client.rfile))) except: break client.wfile.write(h2_conn.data_to_send()) @@ -262,7 +270,7 @@ class TestWithBodies(_Http2TestBase, _Http2ServerBase): done = False while not done: try: - events = h2_conn.receive_data(b''.join(http2_read_raw_frame(client.rfile))) + events = h2_conn.receive_data(b''.join(framereader.http2_read_raw_frame(client.rfile))) except: break client.wfile.write(h2_conn.data_to_send()) @@ -354,7 +362,7 @@ class TestPushPromise(_Http2TestBase, _Http2ServerBase): responses = 0 while not done: try: - raw = b''.join(http2_read_raw_frame(client.rfile)) + raw = b''.join(framereader.http2_read_raw_frame(client.rfile)) events = h2_conn.receive_data(raw) except: break @@ -404,7 +412,7 @@ class TestPushPromise(_Http2TestBase, _Http2ServerBase): responses = 0 while not done: try: - events = h2_conn.receive_data(b''.join(http2_read_raw_frame(client.rfile))) + events = h2_conn.receive_data(b''.join(framereader.http2_read_raw_frame(client.rfile))) except: break client.wfile.write(h2_conn.data_to_send()) @@ -435,6 +443,7 @@ class TestPushPromise(_Http2TestBase, _Http2ServerBase): assert b'regular_stream' in bodies # the other two bodies might not be transmitted before the reset + @requires_alpn class TestConnectionLost(_Http2TestBase, _Http2ServerBase): @@ -468,17 +477,17 @@ class TestConnectionLost(_Http2TestBase, _Http2ServerBase): ]) done = False - ended_streams = 0 - pushed_streams = 0 - responses = 0 while not done: try: - raw = b''.join(http2_read_raw_frame(client.rfile)) - events = h2_conn.receive_data(raw) + raw = b''.join(framereader.http2_read_raw_frame(client.rfile)) + h2_conn.receive_data(raw) + except: + break + try: + client.wfile.write(h2_conn.data_to_send()) + client.wfile.flush() except: break - client.wfile.write(h2_conn.data_to_send()) - client.wfile.flush() if len(self.master.state.flows) == 1: assert self.master.state.flows[0].response is None diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py index e0897135..49c9c909 100644 --- a/test/mitmproxy/test_proxy.py +++ b/test/mitmproxy/test_proxy.py @@ -113,11 +113,10 @@ class TestProcessProxyOptions: "nonexistent") def test_certs(self): - with tutils.tmpdir() as cadir: - self.assert_noerr( - "--cert", - tutils.test_data.path("data/testkey.pem")) - self.assert_err("does not exist", "--cert", "nonexistent") + self.assert_noerr( + "--cert", + tutils.test_data.path("data/testkey.pem")) + self.assert_err("does not exist", "--cert", "nonexistent") def test_auth(self): p = self.assert_noerr("--nonanonymous") diff --git a/test/mitmproxy/test_script.py b/test/mitmproxy/test_script.py index f321d15c..81994780 100644 --- a/test/mitmproxy/test_script.py +++ b/test/mitmproxy/test_script.py @@ -5,9 +5,9 @@ from . import tutils def test_duplicate_flow(): s = flow.State() fm = flow.FlowMaster(None, s) - fm.load_script(tutils.test_data.path("scripts/duplicate_flow.py")) + fm.load_script(tutils.test_data.path("data/scripts/duplicate_flow.py")) f = tutils.tflow() - fm.handle_request(f) + fm.request(f) assert fm.state.flow_count() == 2 assert not fm.state.view[0].request.is_replay assert fm.state.view[1].request.is_replay diff --git a/test/mitmproxy/test_server.py b/test/mitmproxy/test_server.py index 0701d52b..b58c4f44 100644 --- a/test/mitmproxy/test_server.py +++ b/test/mitmproxy/test_server.py @@ -12,6 +12,7 @@ from netlib.http import authentication, http1 from netlib.tutils import raises from pathod import pathoc, pathod +from mitmproxy import controller from mitmproxy.proxy.config import HostMatcher from mitmproxy.exceptions import Kill from mitmproxy.models import Error, HTTPResponse, HTTPFlow @@ -190,8 +191,8 @@ class TcpMixin: assert i_cert == i2_cert == n_cert # Make sure that TCP messages are in the event log. - assert any("305" in m for m in self.master.log) - assert any("306" in m for m in self.master.log) + assert any("305" in m for m in self.master.tlog) + assert any("306" in m for m in self.master.tlog) class AppMixin: @@ -260,7 +261,7 @@ class TestHTTP(tservers.HTTPProxyTest, CommonMixin, AppMixin): p = self.pathoc() assert p.request(req % self.server.urlbase) assert p.request(req % self.server2.urlbase) - assert switched(self.proxy.log) + assert switched(self.proxy.tlog) def test_blank_leading_line(self): p = self.pathoc() @@ -285,7 +286,7 @@ class TestHTTP(tservers.HTTPProxyTest, CommonMixin, AppMixin): self.master.set_stream_large_bodies(None) def test_stream_modify(self): - self.master.load_script(tutils.test_data.path("scripts/stream_modify.py")) + self.master.load_script(tutils.test_data.path("data/scripts/stream_modify.py")) d = self.pathod('200:b"foo"') assert d.content == "bar" self.master.unload_scripts() @@ -499,7 +500,7 @@ class TestHttps2Http(tservers.ReverseProxyTest): def test_sni(self): p = self.pathoc(ssl=True, sni="example.com") assert p.request("get:'/p/200'").status_code == 200 - assert all("Error in handle_sni" not in msg for msg in self.proxy.log) + assert all("Error in handle_sni" not in msg for msg in self.proxy.tlog) def test_http(self): p = self.pathoc(ssl=False) @@ -510,7 +511,7 @@ class TestTransparent(tservers.TransparentProxyTest, CommonMixin, TcpMixin): ssl = False def test_tcp_stream_modify(self): - self.master.load_script(tutils.test_data.path("scripts/tcp_stream_modify.py")) + self.master.load_script(tutils.test_data.path("data/scripts/tcp_stream_modify.py")) self._tcpproxy_on() d = self.pathod('200:b"foo"') @@ -623,7 +624,8 @@ class TestProxySSL(tservers.HTTPProxyTest): class MasterRedirectRequest(tservers.TestMaster): redirect_port = None # Set by TestRedirectRequest - def handle_request(self, f): + @controller.handler + def request(self, f): if f.request.path == "/p/201": # This part should have no impact, but it should also not cause any exceptions. @@ -634,12 +636,13 @@ class MasterRedirectRequest(tservers.TestMaster): # This is the actual redirection. f.request.port = self.redirect_port - super(MasterRedirectRequest, self).handle_request(f) + super(MasterRedirectRequest, self).request(f) - def handle_response(self, f): + @controller.handler + def response(self, f): f.response.content = str(f.client_conn.address.port) f.response.headers["server-conn-id"] = str(f.server_conn.source_address.port) - super(MasterRedirectRequest, self).handle_response(f) + super(MasterRedirectRequest, self).response(f) class TestRedirectRequest(tservers.HTTPProxyTest): @@ -689,10 +692,9 @@ class MasterStreamRequest(tservers.TestMaster): """ Enables the stream flag on the flow for all requests """ - - def handle_responseheaders(self, f): + @controller.handler + def responseheaders(self, f): f.response.stream = True - f.reply() class TestStreamRequest(tservers.HTTPProxyTest): @@ -739,8 +741,8 @@ class TestStreamRequest(tservers.HTTPProxyTest): class MasterFakeResponse(tservers.TestMaster): - - def handle_request(self, f): + @controller.handler + def request(self, f): resp = HTTPResponse.wrap(netlib.tutils.tresp()) f.reply(resp) @@ -761,13 +763,14 @@ class TestServerConnect(tservers.HTTPProxyTest): def test_unnecessary_serverconnect(self): """A replayed/fake response with no_upstream_cert should not connect to an upstream server""" assert self.pathod("200").status_code == 200 - for msg in self.proxy.tmaster.log: + for msg in self.proxy.tmaster.tlog: assert "serverconnect" not in msg class MasterKillRequest(tservers.TestMaster): - def handle_request(self, f): + @controller.handler + def request(self, f): f.reply(Kill) @@ -783,7 +786,8 @@ class TestKillRequest(tservers.HTTPProxyTest): class MasterKillResponse(tservers.TestMaster): - def handle_response(self, f): + @controller.handler + def response(self, f): f.reply(Kill) @@ -812,7 +816,8 @@ class TestTransparentResolveError(tservers.TransparentProxyTest): class MasterIncomplete(tservers.TestMaster): - def handle_request(self, f): + @controller.handler + def request(self, f): resp = HTTPResponse.wrap(netlib.tutils.tresp()) resp.content = None f.reply(resp) @@ -930,7 +935,9 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest): k = [0] # variable scope workaround: put into array _func = getattr(master, attr) - def handler(f): + @controller.handler + def handler(*args): + f = args[-1] k[0] += 1 if not (k[0] in exclude): f.client_conn.finish() @@ -940,13 +947,16 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest): setattr(master, attr, handler) - kill_requests(self.chain[1].tmaster, "handle_request", - exclude=[ - # fail first request - 2, # allow second request - ]) + kill_requests( + self.chain[1].tmaster, + "request", + exclude = [ + # fail first request + 2, # allow second request + ] + ) - kill_requests(self.chain[0].tmaster, "handle_request", + kill_requests(self.chain[0].tmaster, "request", exclude=[ 1, # CONNECT # fail first request @@ -1004,10 +1014,10 @@ class AddUpstreamCertsToClientChainMixin: ssl = True servercert = tutils.test_data.path("data/trusted-server.crt") ssloptions = pathod.SSLOptions( - cn="trusted-cert", - certs=[ - ("trusted-cert", servercert) - ] + cn="trusted-cert", + certs=[ + ("trusted-cert", servercert) + ] ) def test_add_upstream_certs_to_client_chain(self): diff --git a/test/mitmproxy/test_utils.py b/test/mitmproxy/test_utils.py index db7dec4a..c01b5f2a 100644 --- a/test/mitmproxy/test_utils.py +++ b/test/mitmproxy/test_utils.py @@ -13,25 +13,6 @@ def test_format_timestamp_with_milli(): assert utils.format_timestamp_with_milli(utils.timestamp()) -def test_isBin(): - assert not utils.isBin("testing\n\r") - assert utils.isBin("testing\x01") - assert utils.isBin("testing\x0e") - assert utils.isBin("testing\x7f") - - -def test_isXml(): - assert not utils.isXML("foo") - assert utils.isXML("<foo") - assert utils.isXML(" \n<foo") - - -def test_clean_hanging_newline(): - s = "foo\n" - assert utils.clean_hanging_newline(s) == "foo" - assert utils.clean_hanging_newline("foo") == "foo" - - def test_pkg_data(): assert utils.pkg_data.path("console") tutils.raises("does not exist", utils.pkg_data.path, "nonexistent") @@ -43,21 +24,6 @@ def test_pretty_json(): assert not utils.pretty_json("moo") -def test_pretty_duration(): - assert utils.pretty_duration(0.00001) == "0ms" - assert utils.pretty_duration(0.0001) == "0ms" - assert utils.pretty_duration(0.001) == "1ms" - assert utils.pretty_duration(0.01) == "10ms" - assert utils.pretty_duration(0.1) == "100ms" - assert utils.pretty_duration(1) == "1.00s" - assert utils.pretty_duration(10) == "10.0s" - assert utils.pretty_duration(100) == "100s" - assert utils.pretty_duration(1000) == "1000s" - assert utils.pretty_duration(10000) == "10000s" - assert utils.pretty_duration(1.123) == "1.12s" - assert utils.pretty_duration(0.123) == "123ms" - - def test_LRUCache(): cache = utils.LRUCache(2) @@ -89,13 +55,3 @@ def test_LRUCache(): assert len(cache.cacheList) == 2 assert len(cache.cache) == 2 - - -def test_parse_size(): - assert not utils.parse_size("") - assert utils.parse_size("1") == 1 - assert utils.parse_size("1k") == 1024 - assert utils.parse_size("1m") == 1024**2 - assert utils.parse_size("1g") == 1024**3 - tutils.raises(ValueError, utils.parse_size, "1f") - tutils.raises(ValueError, utils.parse_size, "ak") diff --git a/test/mitmproxy/tools/memoryleak.py b/test/mitmproxy/tools/memoryleak.py index 259309a6..c03230da 100644 --- a/test/mitmproxy/tools/memoryleak.py +++ b/test/mitmproxy/tools/memoryleak.py @@ -3,8 +3,8 @@ import threading from pympler import muppy, refbrowser from OpenSSL import SSL # import os -#os.environ["TK_LIBRARY"] = r"C:\Python27\tcl\tcl8.5" -#os.environ["TCL_LIBRARY"] = r"C:\Python27\tcl\tcl8.5" +# os.environ["TK_LIBRARY"] = r"C:\Python27\tcl\tcl8.5" +# os.environ["TCL_LIBRARY"] = r"C:\Python27\tcl\tcl8.5" # Also noteworthy: guppy, objgraph diff --git a/test/mitmproxy/tservers.py b/test/mitmproxy/tservers.py index c9d68cfd..24ebb476 100644 --- a/test/mitmproxy/tservers.py +++ b/test/mitmproxy/tservers.py @@ -39,19 +39,11 @@ class TestMaster(flow.FlowMaster): self.apps.add(errapp, "errapp", 80) self.clear_log() - def handle_request(self, f): - flow.FlowMaster.handle_request(self, f) - f.reply() - - def handle_response(self, f): - flow.FlowMaster.handle_response(self, f) - f.reply() - def clear_log(self): - self.log = [] + self.tlog = [] def add_event(self, message, level=None): - self.log.append(message) + self.tlog.append(message) class ProxyThread(threading.Thread): @@ -68,8 +60,8 @@ class ProxyThread(threading.Thread): return self.tmaster.server.address.port @property - def log(self): - return self.tmaster.log + def tlog(self): + return self.tmaster.tlog def run(self): self.tmaster.run() diff --git a/test/mitmproxy/tutils.py b/test/mitmproxy/tutils.py index 118f849c..d0a09035 100644 --- a/test/mitmproxy/tutils.py +++ b/test/mitmproxy/tutils.py @@ -12,7 +12,7 @@ from unittest.case import SkipTest import netlib.utils import netlib.tutils -from mitmproxy import utils, controller +from mitmproxy import controller from mitmproxy.models import ( ClientConnection, ServerConnection, Error, HTTPRequest, HTTPResponse, HTTPFlow, TCPFlow ) @@ -156,6 +156,7 @@ def chdir(dir): yield os.chdir(orig_dir) + @contextmanager def tmpdir(*args, **kwargs): temp_workdir = tempfile.mkdtemp(*args, **kwargs) diff --git a/test/netlib/data/verificationcerts/generate.py b/test/netlib/data/verificationcerts/generate.py index 9203abbb..6d4d8550 100644 --- a/test/netlib/data/verificationcerts/generate.py +++ b/test/netlib/data/verificationcerts/generate.py @@ -64,5 +64,3 @@ do("openssl req -x509 -new -nodes -batch " "-days 1024 " "-out self-signed.crt".format(SUBJECT) ) - - diff --git a/test/netlib/http/http1/test_assemble.py b/test/netlib/http/http1/test_assemble.py index 8dcbae8e..50d29384 100644 --- a/test/netlib/http/http1/test_assemble.py +++ b/test/netlib/http/http1/test_assemble.py @@ -10,11 +10,11 @@ from netlib.tutils import treq, raises, tresp def test_assemble_request(): - c = assemble_request(treq()) == ( + assert assemble_request(treq()) == ( b"GET /path HTTP/1.1\r\n" b"header: qvalue\r\n" - b"Host: address:22\r\n" - b"Content-Length: 7\r\n" + b"content-length: 7\r\n" + b"host: address:22\r\n" b"\r\n" b"content" ) @@ -32,10 +32,10 @@ def test_assemble_request_head(): def test_assemble_response(): - c = assemble_response(tresp()) == ( + assert assemble_response(tresp()) == ( b"HTTP/1.1 200 OK\r\n" b"header-response: svalue\r\n" - b"Content-Length: 7\r\n" + b"content-length: 7\r\n" b"\r\n" b"message" ) diff --git a/test/netlib/http/http1/test_read.py b/test/netlib/http/http1/test_read.py index d8106904..5285ac1d 100644 --- a/test/netlib/http/http1/test_read.py +++ b/test/netlib/http/http1/test_read.py @@ -1,6 +1,5 @@ from __future__ import absolute_import, print_function, division from io import BytesIO -import textwrap from mock import Mock from netlib.exceptions import HttpException, HttpSyntaxException, HttpReadDisconnect, TcpDisconnect from netlib.http import Headers @@ -8,11 +7,22 @@ from netlib.http.http1.read import ( read_request, read_response, read_request_head, read_response_head, read_body, connection_close, expected_http_body_size, _get_first_line, _read_request_line, _parse_authority_form, _read_response_line, _check_http_version, - _read_headers, _read_chunked + _read_headers, _read_chunked, get_header_tokens ) from netlib.tutils import treq, tresp, raises +def test_get_header_tokens(): + headers = Headers() + assert get_header_tokens(headers, "foo") == [] + headers["foo"] = "bar" + assert get_header_tokens(headers, "foo") == ["bar"] + headers["foo"] = "bar, voing" + assert get_header_tokens(headers, "foo") == ["bar", "voing"] + headers.set_all("foo", ["bar, voing", "oink"]) + assert get_header_tokens(headers, "foo") == ["bar", "voing", "oink"] + + def test_read_request(): rfile = BytesIO(b"GET / HTTP/1.1\r\n\r\nskip") r = read_request(rfile) @@ -106,6 +116,7 @@ class TestReadBody(object): rfile = BytesIO(b"123456") assert list(read_body(rfile, -1, max_chunk_size=1)) == [b"1", b"2", b"3", b"4", b"5", b"6"] + def test_connection_close(): headers = Headers() assert connection_close(b"HTTP/1.0", headers) @@ -121,6 +132,7 @@ def test_connection_close(): assert connection_close(b"HTTP/1.0", headers) assert not connection_close(b"HTTP/1.1", headers) + def test_expected_http_body_size(): # Expect: 100-continue assert expected_http_body_size( @@ -203,6 +215,7 @@ def test_read_request_line(): with raises(HttpReadDisconnect): t(b"") + def test_parse_authority_form(): assert _parse_authority_form(b"foo:42") == (b"foo", 42) with raises(HttpSyntaxException): @@ -302,6 +315,7 @@ class TestReadHeaders(object): headers = self._read(data) assert headers.fields == ((b"bar", b""),) + def test_read_chunked(): req = treq(content=None) req.headers["Transfer-Encoding"] = "chunked" diff --git a/test/netlib/http/http2/test_connections.py b/test/netlib/http/http2/test_connections.py index 7d240c0e..27cc30ba 100644 --- a/test/netlib/http/http2/test_connections.py +++ b/test/netlib/http/http2/test_connections.py @@ -1,16 +1,16 @@ -import OpenSSL import mock import codecs -from hyperframe.frame import * - -from netlib import tcp, http, utils +import hyperframe +from netlib import tcp, http from netlib.tutils import raises from netlib.exceptions import TcpDisconnect from netlib.http.http2.connections import HTTP2Protocol, TCPHandler +from netlib.http.http2 import framereader from ... import tservers + class TestTCPHandlerWrapper: def test_wrapped(self): h = TCPHandler(rfile='foo', wfile='bar') @@ -111,11 +111,11 @@ class TestPerformServerConnectionPreface(tservers.ServerTestBase): self.wfile.flush() # check empty settings frame - raw = utils.http2_read_raw_frame(self.rfile) + raw = framereader.http2_read_raw_frame(self.rfile) assert raw == codecs.decode('00000c040000000000000200000000000300000001', 'hex_codec') # check settings acknowledgement - raw = utils.http2_read_raw_frame(self.rfile) + raw = framereader.http2_read_raw_frame(self.rfile) assert raw == codecs.decode('000000040100000000', 'hex_codec') # send settings acknowledgement @@ -214,19 +214,19 @@ class TestApplySettings(tservers.ServerTestBase): protocol = HTTP2Protocol(c) protocol._apply_settings({ - SettingsFrame.ENABLE_PUSH: 'foo', - SettingsFrame.MAX_CONCURRENT_STREAMS: 'bar', - SettingsFrame.INITIAL_WINDOW_SIZE: 'deadbeef', + hyperframe.frame.SettingsFrame.ENABLE_PUSH: 'foo', + hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS: 'bar', + hyperframe.frame.SettingsFrame.INITIAL_WINDOW_SIZE: 'deadbeef', }) assert c.rfile.safe_read(2) == b"OK" assert protocol.http2_settings[ - SettingsFrame.ENABLE_PUSH] == 'foo' + hyperframe.frame.SettingsFrame.ENABLE_PUSH] == 'foo' assert protocol.http2_settings[ - SettingsFrame.MAX_CONCURRENT_STREAMS] == 'bar' + hyperframe.frame.SettingsFrame.MAX_CONCURRENT_STREAMS] == 'bar' assert protocol.http2_settings[ - SettingsFrame.INITIAL_WINDOW_SIZE] == 'deadbeef' + hyperframe.frame.SettingsFrame.INITIAL_WINDOW_SIZE] == 'deadbeef' class TestCreateHeaders(object): @@ -258,7 +258,7 @@ class TestCreateHeaders(object): (b'server', b'version')]) protocol = HTTP2Protocol(self.c) - protocol.http2_settings[SettingsFrame.MAX_FRAME_SIZE] = 8 + protocol.http2_settings[hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE] = 8 bytes = protocol._create_headers(headers, 1, end_stream=True) assert len(bytes) == 3 assert bytes[0] == codecs.decode('000008010100000001828487408294e783', 'hex_codec') @@ -281,7 +281,7 @@ class TestCreateBody(object): def test_create_body_multiple_frames(self): protocol = HTTP2Protocol(self.c) - protocol.http2_settings[SettingsFrame.MAX_FRAME_SIZE] = 5 + protocol.http2_settings[hyperframe.frame.SettingsFrame.MAX_FRAME_SIZE] = 5 bytes = protocol._create_body(b'foobarmehm42', 1) assert len(bytes) == 3 assert bytes[0] == codecs.decode('000005000000000001666f6f6261', 'hex_codec') @@ -312,7 +312,10 @@ class TestReadRequest(tservers.ServerTestBase): req = protocol.read_request(NotImplemented) assert req.stream_id - assert req.headers.fields == ((b':method', b'GET'), (b':path', b'/'), (b':scheme', b'https')) + assert req.headers.fields == () + assert req.method == "GET" + assert req.path == "/" + assert req.scheme == "https" assert req.content == b'foobar' @@ -461,7 +464,7 @@ class TestAssembleRequest(object): b'', b'/', b"HTTP/2.0", - None, + (), None, )) assert len(bytes) == 1 @@ -476,7 +479,7 @@ class TestAssembleRequest(object): b'', b'/', b"HTTP/2.0", - None, + (), None, ) req.stream_id = 0x42 diff --git a/test/netlib/http/test_authentication.py b/test/netlib/http/test_authentication.py index 1df7cd9c..95d72447 100644 --- a/test/netlib/http/test_authentication.py +++ b/test/netlib/http/test_authentication.py @@ -78,7 +78,7 @@ class TestBasicProxyAuth: assert ba.authenticate(headers) ba.clean(headers) - assert not ba.AUTH_HEADER in headers + assert ba.AUTH_HEADER not in headers headers[ba.AUTH_HEADER] = "" assert not ba.authenticate(headers) diff --git a/test/netlib/http/test_cookies.py b/test/netlib/http/test_cookies.py index 6f84c4ce..83b85656 100644 --- a/test/netlib/http/test_cookies.py +++ b/test/netlib/http/test_cookies.py @@ -184,7 +184,7 @@ def test_parse_set_cookie_pairs(): assert ret == lst s2 = cookies._format_set_cookie_pairs(ret) ret2 = cookies._parse_set_cookie_pairs(s2) - assert ret2 == lst + assert ret2 == lst def test_parse_set_cookie_header(): diff --git a/test/netlib/http/test_headers.py b/test/netlib/http/test_headers.py index cd2ca9d1..51819b86 100644 --- a/test/netlib/http/test_headers.py +++ b/test/netlib/http/test_headers.py @@ -1,4 +1,4 @@ -from netlib.http import Headers +from netlib.http import Headers, parse_content_type from netlib.tutils import raises @@ -72,3 +72,12 @@ class TestHeaders(object): replacements = headers.replace(r"Host: ", "X-Host ") assert replacements == 0 assert headers["Host"] == "example.com" + + +def test_parse_content_type(): + p = parse_content_type + assert p("text/html") == ("text", "html", {}) + assert p("text") is None + + v = p("text/html; charset=UTF-8") + assert v == ('text', 'html', {'charset': 'UTF-8'}) diff --git a/test/netlib/http/test_message.py b/test/netlib/http/test_message.py index 64592921..f5bf7f0c 100644 --- a/test/netlib/http/test_message.py +++ b/test/netlib/http/test_message.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, division -from netlib.http import decoded, Headers -from netlib.tutils import tresp, raises +from netlib.http import decoded +from netlib.tutils import tresp def _test_passthrough_attr(message, attr): diff --git a/test/netlib/http/test_multipart.py b/test/netlib/http/test_multipart.py new file mode 100644 index 00000000..1d7e0062 --- /dev/null +++ b/test/netlib/http/test_multipart.py @@ -0,0 +1,24 @@ +from netlib.http import Headers +from netlib.http import multipart + + +def test_decode(): + boundary = 'somefancyboundary' + headers = Headers( + content_type='multipart/form-data; boundary=' + boundary + ) + content = ( + "--{0}\n" + "Content-Disposition: form-data; name=\"field1\"\n\n" + "value1\n" + "--{0}\n" + "Content-Disposition: form-data; name=\"field2\"\n\n" + "value2\n" + "--{0}--".format(boundary).encode() + ) + + form = multipart.decode(headers, content) + + assert len(form) == 2 + assert form[0] == (b"field1", b"value1") + assert form[1] == (b"field2", b"value2") diff --git a/test/netlib/http/test_request.py b/test/netlib/http/test_request.py index fae7aefe..c03db339 100644 --- a/test/netlib/http/test_request.py +++ b/test/netlib/http/test_request.py @@ -13,7 +13,7 @@ class TestRequestData(object): with raises(ValueError): treq(headers="foobar") - assert isinstance(treq(headers=None).headers, Headers) + assert isinstance(treq(headers=()).headers, Headers) class TestRequestCore(object): diff --git a/test/netlib/http/test_response.py b/test/netlib/http/test_response.py index cfd093d4..b3c2f736 100644 --- a/test/netlib/http/test_response.py +++ b/test/netlib/http/test_response.py @@ -2,12 +2,10 @@ from __future__ import absolute_import, print_function, division import email -import six import time from netlib.http import Headers from netlib.http.cookies import CookieAttrs -from netlib.odict import ODict, ODictCaseless from netlib.tutils import raises, tresp from .test_message import _test_passthrough_attr, _test_decoded_attr @@ -17,7 +15,7 @@ class TestResponseData(object): with raises(ValueError): tresp(headers="foobar") - assert isinstance(tresp(headers=None).headers, Headers) + assert isinstance(tresp(headers=()).headers, Headers) class TestResponseCore(object): @@ -26,7 +24,7 @@ class TestResponseCore(object): """ def test_repr(self): response = tresp() - assert repr(response) == "Response(200 OK, unknown content type, 7B)" + assert repr(response) == "Response(200 OK, unknown content type, 7b)" response.content = None assert repr(response) == "Response(200 OK, no content)" @@ -61,7 +59,8 @@ class TestResponseUtils(object): def test_get_cookies_with_parameters(self): resp = tresp() - resp.headers = Headers(set_cookie="cookiename=cookievalue;domain=example.com;expires=Wed Oct 21 16:29:41 2015;path=/; HttpOnly") + cookie = "cookiename=cookievalue;domain=example.com;expires=Wed Oct 21 16:29:41 2015;path=/; HttpOnly" + resp.headers = Headers(set_cookie=cookie) result = resp.cookies assert len(result) == 1 assert "cookiename" in result diff --git a/test/netlib/http/test_url.py b/test/netlib/http/test_url.py new file mode 100644 index 00000000..26b37230 --- /dev/null +++ b/test/netlib/http/test_url.py @@ -0,0 +1,66 @@ +from netlib import tutils +from netlib.http import url + + +def test_parse(): + with tutils.raises(ValueError): + url.parse("") + + s, h, po, pa = url.parse(b"http://foo.com:8888/test") + assert s == b"http" + assert h == b"foo.com" + assert po == 8888 + assert pa == b"/test" + + s, h, po, pa = url.parse("http://foo/bar") + assert s == b"http" + assert h == b"foo" + assert po == 80 + assert pa == b"/bar" + + s, h, po, pa = url.parse(b"http://user:pass@foo/bar") + assert s == b"http" + assert h == b"foo" + assert po == 80 + assert pa == b"/bar" + + s, h, po, pa = url.parse(b"http://foo") + assert pa == b"/" + + s, h, po, pa = url.parse(b"https://foo") + assert po == 443 + + with tutils.raises(ValueError): + url.parse(b"https://foo:bar") + + # Invalid IDNA + with tutils.raises(ValueError): + url.parse("http://\xfafoo") + # Invalid PATH + with tutils.raises(ValueError): + url.parse("http:/\xc6/localhost:56121") + # Null byte in host + with tutils.raises(ValueError): + url.parse("http://foo\0") + # Port out of range + _, _, port, _ = url.parse("http://foo:999999") + assert port == 80 + # Invalid IPv6 URL - see http://www.ietf.org/rfc/rfc2732.txt + with tutils.raises(ValueError): + url.parse('http://lo[calhost') + + +def test_unparse(): + assert url.unparse("http", "foo.com", 99, "") == "http://foo.com:99" + assert url.unparse("http", "foo.com", 80, "/bar") == "http://foo.com/bar" + assert url.unparse("https", "foo.com", 80, "") == "https://foo.com:80" + assert url.unparse("https", "foo.com", 443, "") == "https://foo.com" + + +def test_urlencode(): + assert url.encode([('foo', 'bar')]) + + +def test_urldecode(): + s = "one=two&three=four" + assert len(url.decode(s)) == 2 diff --git a/test/netlib/test_basetypes.py b/test/netlib/test_basetypes.py new file mode 100644 index 00000000..aa415784 --- /dev/null +++ b/test/netlib/test_basetypes.py @@ -0,0 +1,28 @@ +from netlib import basetypes + + +class SerializableDummy(basetypes.Serializable): + def __init__(self, i): + self.i = i + + def get_state(self): + return self.i + + def set_state(self, i): + self.i = i + + def from_state(self, state): + return type(self)(state) + + +class TestSerializable: + + def test_copy(self): + a = SerializableDummy(42) + assert a.i == 42 + b = a.copy() + assert b.i == 42 + + a.set_state(1) + assert a.i == 1 + assert b.i == 42 diff --git a/test/netlib/test_human.py b/test/netlib/test_human.py new file mode 100644 index 00000000..2a5c2a85 --- /dev/null +++ b/test/netlib/test_human.py @@ -0,0 +1,36 @@ +from netlib import human, tutils + + +def test_parse_size(): + assert human.parse_size("0") == 0 + assert human.parse_size("0b") == 0 + assert human.parse_size("1") == 1 + assert human.parse_size("1k") == 1024 + assert human.parse_size("1m") == 1024**2 + assert human.parse_size("1g") == 1024**3 + tutils.raises(ValueError, human.parse_size, "1f") + tutils.raises(ValueError, human.parse_size, "ak") + + +def test_pretty_size(): + assert human.pretty_size(0) == "0b" + assert human.pretty_size(100) == "100b" + assert human.pretty_size(1024) == "1k" + assert human.pretty_size(1024 + (1024 / 2.0)) == "1.5k" + assert human.pretty_size(1024 * 1024) == "1m" + assert human.pretty_size(10 * 1024 * 1024) == "10m" + + +def test_pretty_duration(): + assert human.pretty_duration(0.00001) == "0ms" + assert human.pretty_duration(0.0001) == "0ms" + assert human.pretty_duration(0.001) == "1ms" + assert human.pretty_duration(0.01) == "10ms" + assert human.pretty_duration(0.1) == "100ms" + assert human.pretty_duration(1) == "1.00s" + assert human.pretty_duration(10) == "10.0s" + assert human.pretty_duration(100) == "100s" + assert human.pretty_duration(1000) == "1000s" + assert human.pretty_duration(10000) == "10000s" + assert human.pretty_duration(1.123) == "1.12s" + assert human.pretty_duration(0.123) == "123ms" diff --git a/test/netlib/test_multidict.py b/test/netlib/test_multidict.py index 5bb65e3f..7319f1c5 100644 --- a/test/netlib/test_multidict.py +++ b/test/netlib/test_multidict.py @@ -49,7 +49,7 @@ class TestMultiDict(object): assert md["foo"] == "bar" with tutils.raises(KeyError): - _ = md["bar"] + md["bar"] md_multi = TMultiDict( [("foo", "a"), ("foo", "b")] diff --git a/test/netlib/test_socks.py b/test/netlib/test_socks.py index 486b975b..17e08054 100644 --- a/test/netlib/test_socks.py +++ b/test/netlib/test_socks.py @@ -1,6 +1,5 @@ import ipaddress from io import BytesIO -import socket from netlib import socks, tcp, tutils diff --git a/test/netlib/test_strutils.py b/test/netlib/test_strutils.py new file mode 100644 index 00000000..734265c4 --- /dev/null +++ b/test/netlib/test_strutils.py @@ -0,0 +1,63 @@ +# coding=utf-8 + +from netlib import strutils + + +def test_clean_bin(): + assert strutils.clean_bin(b"one") == b"one" + assert strutils.clean_bin(b"\00ne") == b".ne" + assert strutils.clean_bin(b"\nne") == b"\nne" + assert strutils.clean_bin(b"\nne", False) == b".ne" + assert strutils.clean_bin(u"\u2605".encode("utf8")) == b"..." + + assert strutils.clean_bin(u"one") == u"one" + assert strutils.clean_bin(u"\00ne") == u".ne" + assert strutils.clean_bin(u"\nne") == u"\nne" + assert strutils.clean_bin(u"\nne", False) == u".ne" + assert strutils.clean_bin(u"\u2605") == u"\u2605" + + +def test_safe_subn(): + assert strutils.safe_subn("foo", u"bar", "\xc2foo") + + +def test_bytes_to_escaped_str(): + assert strutils.bytes_to_escaped_str(b"foo") == "foo" + assert strutils.bytes_to_escaped_str(b"\b") == r"\x08" + assert strutils.bytes_to_escaped_str(br"&!?=\)") == r"&!?=\\)" + assert strutils.bytes_to_escaped_str(b'\xc3\xbc') == r"\xc3\xbc" + assert strutils.bytes_to_escaped_str(b"'") == r"\'" + assert strutils.bytes_to_escaped_str(b'"') == r'"' + + +def test_escaped_str_to_bytes(): + assert strutils.escaped_str_to_bytes("foo") == b"foo" + assert strutils.escaped_str_to_bytes("\x08") == b"\b" + assert strutils.escaped_str_to_bytes("&!?=\\\\)") == br"&!?=\)" + assert strutils.escaped_str_to_bytes("ü") == b'\xc3\xbc' + assert strutils.escaped_str_to_bytes(u"\\x08") == b"\b" + assert strutils.escaped_str_to_bytes(u"&!?=\\\\)") == br"&!?=\)" + assert strutils.escaped_str_to_bytes(u"ü") == b'\xc3\xbc' + + +def test_isBin(): + assert not strutils.isBin("testing\n\r") + assert strutils.isBin("testing\x01") + assert strutils.isBin("testing\x0e") + assert strutils.isBin("testing\x7f") + + +def test_isXml(): + assert not strutils.isXML("foo") + assert strutils.isXML("<foo") + assert strutils.isXML(" \n<foo") + + +def test_clean_hanging_newline(): + s = "foo\n" + assert strutils.clean_hanging_newline(s) == "foo" + assert strutils.clean_hanging_newline("foo") == "foo" + + +def test_hexdump(): + assert list(strutils.hexdump(b"one\0" * 10)) diff --git a/test/netlib/test_tcp.py b/test/netlib/test_tcp.py index 4b4bbb92..083360b4 100644 --- a/test/netlib/test_tcp.py +++ b/test/netlib/test_tcp.py @@ -8,7 +8,6 @@ import threading import mock from OpenSSL import SSL -import OpenSSL from netlib import tcp, certutils, tutils from netlib.exceptions import InvalidCertificateException, TcpReadIncomplete, TlsException, \ @@ -16,6 +15,7 @@ from netlib.exceptions import InvalidCertificateException, TcpReadIncomplete, Tl from . import tservers + class EchoHandler(tcp.BaseHandler): sni = None diff --git a/test/netlib/test_utils.py b/test/netlib/test_utils.py index 1d8f7b0f..eaa66f13 100644 --- a/test/netlib/test_utils.py +++ b/test/netlib/test_utils.py @@ -1,6 +1,7 @@ # coding=utf-8 + from netlib import utils, tutils -from netlib.http import Headers + def test_bidi(): b = utils.BiDi(a=1, b=2) @@ -9,179 +10,3 @@ def test_bidi(): assert b.get_name(5) is None tutils.raises(AttributeError, getattr, b, "c") tutils.raises(ValueError, utils.BiDi, one=1, two=1) - - -def test_hexdump(): - assert list(utils.hexdump(b"one\0" * 10)) - - -def test_clean_bin(): - assert utils.clean_bin(b"one") == b"one" - assert utils.clean_bin(b"\00ne") == b".ne" - assert utils.clean_bin(b"\nne") == b"\nne" - assert utils.clean_bin(b"\nne", False) == b".ne" - assert utils.clean_bin(u"\u2605".encode("utf8")) == b"..." - - assert utils.clean_bin(u"one") == u"one" - assert utils.clean_bin(u"\00ne") == u".ne" - assert utils.clean_bin(u"\nne") == u"\nne" - assert utils.clean_bin(u"\nne", False) == u".ne" - assert utils.clean_bin(u"\u2605") == u"\u2605" - - -def test_pretty_size(): - assert utils.pretty_size(100) == "100B" - assert utils.pretty_size(1024) == "1kB" - assert utils.pretty_size(1024 + (1024 / 2.0)) == "1.5kB" - assert utils.pretty_size(1024 * 1024) == "1MB" - - -def test_parse_url(): - with tutils.raises(ValueError): - utils.parse_url("") - - s, h, po, pa = utils.parse_url(b"http://foo.com:8888/test") - assert s == b"http" - assert h == b"foo.com" - assert po == 8888 - assert pa == b"/test" - - s, h, po, pa = utils.parse_url("http://foo/bar") - assert s == b"http" - assert h == b"foo" - assert po == 80 - assert pa == b"/bar" - - s, h, po, pa = utils.parse_url(b"http://user:pass@foo/bar") - assert s == b"http" - assert h == b"foo" - assert po == 80 - assert pa == b"/bar" - - s, h, po, pa = utils.parse_url(b"http://foo") - assert pa == b"/" - - s, h, po, pa = utils.parse_url(b"https://foo") - assert po == 443 - - with tutils.raises(ValueError): - utils.parse_url(b"https://foo:bar") - - # Invalid IDNA - with tutils.raises(ValueError): - utils.parse_url("http://\xfafoo") - # Invalid PATH - with tutils.raises(ValueError): - utils.parse_url("http:/\xc6/localhost:56121") - # Null byte in host - with tutils.raises(ValueError): - utils.parse_url("http://foo\0") - # Port out of range - _, _, port, _ = utils.parse_url("http://foo:999999") - assert port == 80 - # Invalid IPv6 URL - see http://www.ietf.org/rfc/rfc2732.txt - with tutils.raises(ValueError): - utils.parse_url('http://lo[calhost') - - -def test_unparse_url(): - assert utils.unparse_url("http", "foo.com", 99, "") == "http://foo.com:99" - assert utils.unparse_url("http", "foo.com", 80, "/bar") == "http://foo.com/bar" - assert utils.unparse_url("https", "foo.com", 80, "") == "https://foo.com:80" - assert utils.unparse_url("https", "foo.com", 443, "") == "https://foo.com" - - -def test_urlencode(): - assert utils.urlencode([('foo', 'bar')]) - - -def test_urldecode(): - s = "one=two&three=four" - assert len(utils.urldecode(s)) == 2 - - -def test_get_header_tokens(): - headers = Headers() - assert utils.get_header_tokens(headers, "foo") == [] - headers["foo"] = "bar" - assert utils.get_header_tokens(headers, "foo") == ["bar"] - headers["foo"] = "bar, voing" - assert utils.get_header_tokens(headers, "foo") == ["bar", "voing"] - headers.set_all("foo", ["bar, voing", "oink"]) - assert utils.get_header_tokens(headers, "foo") == ["bar", "voing", "oink"] - - -def test_multipartdecode(): - boundary = 'somefancyboundary' - headers = Headers( - content_type='multipart/form-data; boundary=' + boundary - ) - content = ( - "--{0}\n" - "Content-Disposition: form-data; name=\"field1\"\n\n" - "value1\n" - "--{0}\n" - "Content-Disposition: form-data; name=\"field2\"\n\n" - "value2\n" - "--{0}--".format(boundary).encode() - ) - - form = utils.multipartdecode(headers, content) - - assert len(form) == 2 - assert form[0] == (b"field1", b"value1") - assert form[1] == (b"field2", b"value2") - - -def test_parse_content_type(): - p = utils.parse_content_type - assert p("text/html") == ("text", "html", {}) - assert p("text") is None - - v = p("text/html; charset=UTF-8") - assert v == ('text', 'html', {'charset': 'UTF-8'}) - - -class SerializableDummy(utils.Serializable): - def __init__(self, i): - self.i = i - - def get_state(self): - return self.i - - def set_state(self, i): - self.i = i - - def from_state(self, state): - return type(self)(state) - - -class TestSerializable: - - def test_copy(self): - a = SerializableDummy(42) - assert a.i == 42 - b = a.copy() - assert b.i == 42 - - a.set_state(1) - assert a.i == 1 - assert b.i == 42 - - -def test_safe_subn(): - assert utils.safe_subn("foo", u"bar", "\xc2foo") - - -def test_bytes_to_escaped_str(): - assert utils.bytes_to_escaped_str(b"foo") == "foo" - assert utils.bytes_to_escaped_str(b"\b") == r"\x08" - assert utils.bytes_to_escaped_str(br"&!?=\)") == r"&!?=\\)" - assert utils.bytes_to_escaped_str(b'\xc3\xbc') == r"\xc3\xbc" - - -def test_escaped_str_to_bytes(): - assert utils.escaped_str_to_bytes("foo") == b"foo" - assert utils.escaped_str_to_bytes(r"\x08") == b"\b" - assert utils.escaped_str_to_bytes(r"&!?=\\)") == br"&!?=\)" - assert utils.escaped_str_to_bytes(r"ü") == b'\xc3\xbc' diff --git a/test/netlib/test_version_check.py b/test/netlib/test_version_check.py index 680f80e0..fa6b19e5 100644 --- a/test/netlib/test_version_check.py +++ b/test/netlib/test_version_check.py @@ -1,6 +1,6 @@ from io import StringIO import mock -from netlib import version_check, version +from netlib import version_check @mock.patch("sys.exit") diff --git a/test/netlib/test_wsgi.py b/test/netlib/test_wsgi.py index 8c782b27..5c61f81c 100644 --- a/test/netlib/test_wsgi.py +++ b/test/netlib/test_wsgi.py @@ -11,7 +11,7 @@ def tflow(): class ExampleApp: - + def __init__(self): self.called = False diff --git a/test/netlib/websockets/test_websockets.py b/test/netlib/websockets/test_websockets.py index a7d782a4..50fa26e6 100644 --- a/test/netlib/websockets/test_websockets.py +++ b/test/netlib/websockets/test_websockets.py @@ -2,13 +2,16 @@ import os from netlib.http.http1 import read_response, read_request -from netlib import tcp, websockets, http, tutils +from netlib import tcp +from netlib import tutils +from netlib import websockets from netlib.http import status_codes from netlib.tutils import treq -from netlib.exceptions import * +from netlib import exceptions from .. import tservers + class WebSocketsEchoHandler(tcp.BaseHandler): def __init__(self, connection, address, server): @@ -117,8 +120,8 @@ class TestWebSockets(tservers.ServerTestBase): default builder should always generate valid frames """ msg = self.random_bytes() - client_frame = websockets.Frame.default(msg, from_client=True) - server_frame = websockets.Frame.default(msg, from_client=False) + assert websockets.Frame.default(msg, from_client=True) + assert websockets.Frame.default(msg, from_client=False) def test_serialization_bijection(self): """ @@ -174,7 +177,7 @@ class TestBadHandshake(tservers.ServerTestBase): handler = BadHandshakeHandler def test(self): - with tutils.raises(TcpDisconnect): + with tutils.raises(exceptions.TcpDisconnect): client = WebSocketsClient(("127.0.0.1", self.port)) client.connect() client.send_message(b"hello") diff --git a/test/pathod/test_app.py b/test/pathod/test_app.py index ac89c44c..fbaa773c 100644 --- a/test/pathod/test_app.py +++ b/test/pathod/test_app.py @@ -11,11 +11,11 @@ class TestApp(tutils.DaemonTests): def test_about(self): r = self.getpath("/about") - assert r.ok + assert r.status_code == 200 def test_download(self): r = self.getpath("/download") - assert r.ok + assert r.status_code == 200 def test_docs(self): assert self.getpath("/docs/pathod").status_code == 200 @@ -27,7 +27,7 @@ class TestApp(tutils.DaemonTests): def test_log(self): assert self.getpath("/log").status_code == 200 assert self.get("200:da").status_code == 200 - id = self.d.log()[0]["id"] + id = self.d.expect_log(1)[0]["id"] assert self.getpath("/log").status_code == 200 assert self.getpath("/log/%s" % id).status_code == 200 assert self.getpath("/log/9999999").status_code == 404 diff --git a/test/pathod/test_language_actions.py b/test/pathod/test_language_actions.py index c2e15189..81d2155d 100644 --- a/test/pathod/test_language_actions.py +++ b/test/pathod/test_language_actions.py @@ -68,9 +68,9 @@ class TestInject: def test_spec(self): e = actions.InjectAt.expr() v = e.parseString("i0,'foo'")[0] - assert v.spec() == 'i0,"foo"' + assert v.spec() == "i0,'foo'" - def test_spec(self): + def test_spec2(self): e = actions.InjectAt.expr() v = e.parseString("i0,@100")[0] v2 = v.freeze({}) diff --git a/test/pathod/test_language_base.py b/test/pathod/test_language_base.py index 64d4af1f..47e51bb0 100644 --- a/test/pathod/test_language_base.py +++ b/test/pathod/test_language_base.py @@ -41,11 +41,11 @@ class TestTokValueLiteral: def test_espr(self): v = base.TokValueLiteral("foo") assert v.expr() - assert v.val == "foo" + assert v.val == b"foo" v = base.TokValueLiteral("foo\n") assert v.expr() - assert v.val == "foo\n" + assert v.val == b"foo\n" assert repr(v) def test_spec(self): @@ -67,7 +67,7 @@ class TestTokValueLiteral: def test_roundtrip(self): self.roundtrip("'") - self.roundtrip('\'') + self.roundtrip(r"\'") self.roundtrip("a") self.roundtrip("\"") # self.roundtrip("\\") @@ -171,19 +171,19 @@ class TestMisc: def test_generators(self): v = base.TokValue.parseString("'val'")[0] g = v.get_generator({}) - assert g[:] == "val" + assert g[:] == b"val" def test_value(self): - assert base.TokValue.parseString("'val'")[0].val == "val" - assert base.TokValue.parseString('"val"')[0].val == "val" - assert base.TokValue.parseString('"\'val\'"')[0].val == "'val'" + assert base.TokValue.parseString("'val'")[0].val == b"val" + assert base.TokValue.parseString('"val"')[0].val == b"val" + assert base.TokValue.parseString('"\'val\'"')[0].val == b"'val'" - def test_value(self): + def test_value2(self): class TT(base.Value): preamble = "m" e = TT.expr() v = e.parseString("m'msg'")[0] - assert v.value.val == "msg" + assert v.value.val == b"msg" s = v.spec() assert s == e.parseString(s)[0].spec() @@ -235,8 +235,8 @@ class TestKeyValue: def test_simple(self): e = TKeyValue.expr() v = e.parseString("h'foo'='bar'")[0] - assert v.key.val == "foo" - assert v.value.val == "bar" + assert v.key.val == b"foo" + assert v.value.val == b"bar" v2 = e.parseString(v.spec())[0] assert v2.key.val == v.key.val @@ -289,9 +289,9 @@ def test_options_or_value(): "three" ] e = TT.expr() - assert e.parseString("one")[0].value.val == "one" - assert e.parseString("'foo'")[0].value.val == "foo" - assert e.parseString("'get'")[0].value.val == "get" + assert e.parseString("one")[0].value.val == b"one" + assert e.parseString("'foo'")[0].value.val == b"foo" + assert e.parseString("'get'")[0].value.val == b"get" assert e.parseString("one")[0].spec() == "one" assert e.parseString("'foo'")[0].spec() == "'foo'" diff --git a/test/pathod/test_language_generators.py b/test/pathod/test_language_generators.py index 0fceae85..51f55991 100644 --- a/test/pathod/test_language_generators.py +++ b/test/pathod/test_language_generators.py @@ -7,24 +7,27 @@ import tutils def test_randomgenerator(): g = generators.RandomGenerator("bytes", 100) assert repr(g) + assert g[0] + assert len(g[0]) == 1 assert len(g[:10]) == 10 assert len(g[1:10]) == 9 assert len(g[:1000]) == 100 assert len(g[1000:1001]) == 0 - assert g[0] def test_filegenerator(): with tutils.tmpdir() as t: path = os.path.join(t, "foo") f = open(path, "wb") - f.write("x" * 10000) + f.write(b"x" * 10000) f.close() g = generators.FileGenerator(path) assert len(g) == 10000 - assert g[0] == "x" - assert g[-1] == "x" - assert g[0:5] == "xxxxx" + assert g[0] == b"x" + assert g[-1] == b"x" + assert g[0:5] == b"xxxxx" + assert len(g[1:10]) == 9 + assert len(g[10000:10001]) == 0 assert repr(g) # remove all references to FileGenerator instance to close the file # handle. diff --git a/test/pathod/test_language_http2.py b/test/pathod/test_language_http2.py index abfe4606..de256626 100644 --- a/test/pathod/test_language_http2.py +++ b/test/pathod/test_language_http2.py @@ -5,7 +5,7 @@ from netlib import tcp from netlib.http import user_agents from pathod import language -from pathod.language import http2, base +from pathod.language import http2 import tutils @@ -141,7 +141,6 @@ class TestRequest: assert isinstance(r.tokens[2], http2.NestedResponse) assert r.values(default_settings()) - def test_render_with_body(self): s = StringIO() r = parse_request("GET:'/foo':bfoobar") diff --git a/test/pathod/test_log.py b/test/pathod/test_log.py index d91b8bb1..29801eb3 100644 --- a/test/pathod/test_log.py +++ b/test/pathod/test_log.py @@ -1,10 +1,10 @@ -import StringIO from pathod import log from netlib.exceptions import TcpDisconnect -import netlib.tcp +import six -class DummyIO(StringIO.StringIO): + +class DummyIO(six.StringIO): def start_log(self, *args, **kwargs): pass diff --git a/test/pathod/test_pathoc.py b/test/pathod/test_pathoc.py index 4e8c89c5..6e36c4bf 100644 --- a/test/pathod/test_pathoc.py +++ b/test/pathod/test_pathoc.py @@ -1,12 +1,12 @@ import json from six.moves import cStringIO as StringIO import re -import OpenSSL import pytest from mock import Mock -from netlib import tcp, http, socks -from netlib.exceptions import HttpException, TcpException, NetlibException +from netlib import http +from netlib import tcp +from netlib.exceptions import NetlibException from netlib.http import http1, http2 from pathod import pathoc, test, version, pathod, language @@ -147,7 +147,7 @@ class TestDaemon(_TestDaemon): tutils.raises("ssl handshake", c.connect) def test_showssl(self): - assert not "certificate chain" in self.tval( + assert "certificate chain" not in self.tval( ["get:/p/200"], showssl=True) @@ -170,7 +170,7 @@ class TestDaemon(_TestDaemon): showresp=True, timeout=1 ) - assert not "HTTP" in self.tval( + assert "HTTP" not in self.tval( ["get:'/p/200:p3,100'"], showresp=True, timeout=1, diff --git a/test/pathod/test_pathoc_cmdline.py b/test/pathod/test_pathoc_cmdline.py index f527e861..35909325 100644 --- a/test/pathod/test_pathoc_cmdline.py +++ b/test/pathod/test_pathoc_cmdline.py @@ -29,13 +29,13 @@ def test_pathoc(perror): assert a.connect_to == ["foo", 10] a = cmdline.args_pathoc(["pathoc", "foo.com", "get:/", "--http2"]) - assert a.use_http2 == True - assert a.ssl == True + assert a.use_http2 is True + assert a.ssl is True a = cmdline.args_pathoc(["pathoc", "foo.com", "get:/", "--http2-skip-connection-preface"]) - assert a.use_http2 == True - assert a.ssl == True - assert a.http2_skip_connection_preface == True + assert a.use_http2 is True + assert a.ssl is True + assert a.http2_skip_connection_preface is True a = cmdline.args_pathoc(["pathoc", "-c", "foo", "foo.com:8888", "get:/"]) assert perror.called diff --git a/test/pathod/test_pathod.py b/test/pathod/test_pathod.py index 05a3962e..ec9c169f 100644 --- a/test/pathod/test_pathod.py +++ b/test/pathod/test_pathod.py @@ -1,8 +1,7 @@ from six.moves import cStringIO as StringIO -import pytest -from pathod import pathod, version -from netlib import tcp, http +from pathod import pathod +from netlib import tcp from netlib.exceptions import HttpException, TlsException import tutils @@ -51,7 +50,7 @@ class TestNoApi(tutils.DaemonTests): assert self.getpath("/log").status_code == 404 r = self.getpath("/") assert r.status_code == 200 - assert not "Log" in r.content + assert "Log" not in r.content class TestNotAfterConnect(tutils.DaemonTests): @@ -110,7 +109,7 @@ class TestHexdump(tutils.DaemonTests): hexdump = True def test_hexdump(self): - r = self.get(r"200:b'\xf0'") + assert self.get(r"200:b'\xf0'") class TestNocraft(tutils.DaemonTests): @@ -125,11 +124,10 @@ class TestNocraft(tutils.DaemonTests): class CommonTests(tutils.DaemonTests): def test_binarydata(self): - r = self.get(r"200:b'\xf0'") - l = self.d.last_log() + assert self.get(r"200:b'\xf0'") + assert self.d.last_log() # FIXME: Other binary data elements - @pytest.mark.skip(reason="race condition") def test_sizelimit(self): r = self.get("200:b@1g") assert r.status_code == 800 @@ -140,21 +138,15 @@ class CommonTests(tutils.DaemonTests): r, _ = self.pathoc([r"get:'/p/200':i0,'\r\n'"]) assert r[0].status_code == 200 - def test_info(self): - assert tuple(self.d.info()["version"]) == version.IVERSION - - @pytest.mark.skip(reason="race condition") def test_logs(self): - assert self.d.clear_log() - assert not self.d.last_log() - rsp = self.get("202:da") - assert len(self.d.log()) == 1 - assert self.d.clear_log() + self.d.clear_log() + assert self.get("202:da") + assert self.d.expect_log(1) + self.d.clear_log() assert len(self.d.log()) == 0 def test_disconnect(self): - rsp = self.get("202:b@100k:d200") - assert len(rsp.content) < 200 + tutils.raises("unexpected eof", self.get, "202:b@100k:d200") def test_parserr(self): rsp = self.get("400:msg,b:") @@ -166,7 +158,7 @@ class CommonTests(tutils.DaemonTests): assert rsp.content.strip() == "testfile" def test_anchor(self): - rsp = self.getpath("anchor/foo") + rsp = self.getpath("/anchor/foo") assert rsp.status_code == 202 def test_invalid_first_line(self): @@ -223,7 +215,6 @@ class CommonTests(tutils.DaemonTests): ) assert r[1].payload == "test" - @pytest.mark.skip(reason="race condition") def test_websocket_frame_reflect_error(self): r, _ = self.pathoc( ["ws:/p/", "wf:-mask:knone:f'wf:b@10':i13,'a'"], @@ -233,7 +224,6 @@ class CommonTests(tutils.DaemonTests): # FIXME: Race Condition? assert "Parse error" in self.d.text_log() - @pytest.mark.skip(reason="race condition") def test_websocket_frame_disconnect_error(self): self.pathoc(["ws:/p/", "wf:b@10:d3"], ws_read_limit=0) assert self.d.last_log() diff --git a/test/pathod/test_test.py b/test/pathod/test_test.py index cee286a4..6399894e 100644 --- a/test/pathod/test_test.py +++ b/test/pathod/test_test.py @@ -2,6 +2,10 @@ import logging import requests from pathod import test import tutils + +import requests.packages.urllib3 + +requests.packages.urllib3.disable_warnings() logging.disable(logging.CRITICAL) diff --git a/test/pathod/test_utils.py b/test/pathod/test_utils.py index 4dcedf6e..a46a523a 100644 --- a/test/pathod/test_utils.py +++ b/test/pathod/test_utils.py @@ -11,13 +11,6 @@ def test_membool(): assert m.v == 2 -def test_parse_size(): - assert utils.parse_size("100") == 100 - assert utils.parse_size("100k") == 100 * 1024 - tutils.raises("invalid size spec", utils.parse_size, "foo") - tutils.raises("invalid size spec", utils.parse_size, "100kk") - - def test_parse_anchor_spec(): assert utils.parse_anchor_spec("foo=200") == ("foo", "200") assert utils.parse_anchor_spec("foo") is None @@ -25,15 +18,3 @@ def test_parse_anchor_spec(): def test_data_path(): tutils.raises(ValueError, utils.data.path, "nonexistent") - - -def test_inner_repr(): - assert utils.inner_repr("\x66") == "\x66" - assert utils.inner_repr(u"foo") == "foo" - - -def test_escape_unprintables(): - s = "".join([chr(i) for i in range(255)]) - e = utils.escape_unprintables(s) - assert e.encode('ascii') - assert not "PATHOD_MARKER" in e diff --git a/test/pathod/tutils.py b/test/pathod/tutils.py index f6ed3efb..b9f38d86 100644 --- a/test/pathod/tutils.py +++ b/test/pathod/tutils.py @@ -1,12 +1,19 @@ import tempfile import re import shutil +import requests from six.moves import cStringIO as StringIO +import urllib -import netlib -from pathod import utils, test, pathoc, pathod, language from netlib import tcp -import requests +from netlib import utils +from netlib import tutils + +from pathod import language +from pathod import pathoc +from pathod import pathod +from pathod import test + def treader(bytes): """ @@ -57,10 +64,11 @@ class DaemonTests(object): shutil.rmtree(cls.confdir) def teardown(self): + self.d.wait_for_silence() if not (self.noweb or self.noapi): self.d.clear_log() - def getpath(self, path, params=None): + def _getpath(self, path, params=None): scheme = "https" if self.ssl else "http" resp = requests.get( "%s://localhost:%s/%s" % ( @@ -73,9 +81,29 @@ class DaemonTests(object): ) return resp + def getpath(self, path, params=None): + logfp = StringIO() + c = pathoc.Pathoc( + ("localhost", self.d.port), + ssl=self.ssl, + fp=logfp, + ) + with c.connect(): + if params: + path = path + "?" + urllib.urlencode(params) + resp = c.request("get:%s" % path) + return resp + def get(self, spec): - resp = requests.get(self.d.p(spec), verify=False) - return resp + logfp = StringIO() + c = pathoc.Pathoc( + ("localhost", self.d.port), + ssl=self.ssl, + fp=logfp, + ) + with c.connect(): + resp = c.request("get:/p/%s" % urllib.quote(spec).encode("string_escape")) + return resp def pathoc( self, @@ -100,23 +128,23 @@ class DaemonTests(object): fp=logfp, use_http2=use_http2, ) - c.connect(connect_to) - ret = [] - for i in specs: - resp = c.request(i) - if resp: - ret.append(resp) - for frm in c.wait(): - ret.append(frm) - c.stop() - return ret, logfp.getvalue() + with c.connect(connect_to): + ret = [] + for i in specs: + resp = c.request(i) + if resp: + ret.append(resp) + for frm in c.wait(): + ret.append(frm) + c.stop() + return ret, logfp.getvalue() -tmpdir = netlib.tutils.tmpdir +tmpdir = tutils.tmpdir -raises = netlib.tutils.raises +raises = tutils.raises -test_data = netlib.utils.Data(__name__) +test_data = utils.Data(__name__) def render(r, settings=language.Settings()): diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..08542426 --- /dev/null +++ b/tox.ini @@ -0,0 +1,15 @@ +[tox] +envlist = py27, py35, lint + +[testenv] +deps = -rrequirements.txt + +[testenv:py27] +commands = py.test -n 8 --timeout 60 ./test + +[testenv:py35] +commands = py.test -n 8 --timeout 60 ./test/netlib ./test/mitmproxy/script ./test/pathod/test_utils.py ./test/pathod/test_log.py + +[testenv:lint] +deps = flake8 +commands = flake8 --count mitmproxy netlib pathod examples test diff --git a/web/.babelrc b/web/.babelrc index 5dd7708c..e2d67e33 100644 --- a/web/.babelrc +++ b/web/.babelrc @@ -1,4 +1,4 @@ { "presets": ["es2015", "react"], - "plugins": ["transform-class-properties"] + "plugins": ["transform-class-properties", "transform-object-rest-spread"] }
\ No newline at end of file @@ -3,4 +3,4 @@ Starting up - npm install - gulp -- run mitmweb and open http://localhost:8081/
\ No newline at end of file +- run mitmweb and open http://localhost:8081/ diff --git a/web/package.json b/web/package.json index 2eaac445..88e976e1 100644 --- a/web/package.json +++ b/web/package.json @@ -30,6 +30,7 @@ "babel-core": "^6.7.7", "babel-jest": "^12.0.2", "babel-plugin-transform-class-properties": "^6.6.0", + "babel-plugin-transform-object-rest-spread": "^6.8.0", "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", "babelify": "^7.3.0", diff --git a/web/src/css/header.less b/web/src/css/header.less index 065471d1..b1bd9c04 100644 --- a/web/src/css/header.less +++ b/web/src/css/header.less @@ -18,6 +18,7 @@ header { .filter-input { .make-sm-column(3, @menu-row-gutter-width); + margin-bottom:5px; } .filter-input .popover { @@ -32,6 +33,19 @@ header { } } -.menu .btn { - margin: 2px 2px 2px 2px; +.menu .toggle-btn { + .make-xs-column(4, @menu-row-gutter-width); + .make-sm-column(3, @menu-row-gutter-width); + .make-lg-column(2, @menu-row-gutter-width); + margin-bottom:5px; +} + +.menu .toggle-btn .btn { + width: 100%; +} + +.menu .toggle-input-btn { + .make-sm-column(6, @menu-row-gutter-width); + .make-lg-column(4, @menu-row-gutter-width); + margin-bottom:5px; }
\ No newline at end of file diff --git a/web/src/js/components/common.js b/web/src/js/components/common.js index 21ca454f..87c34ffc 100644 --- a/web/src/js/components/common.js +++ b/web/src/js/components/common.js @@ -1,33 +1,8 @@ import React from "react" import ReactDOM from "react-dom" +import {Key} from "../utils.js"; import _ from "lodash" -export var Router = { - contextTypes: { - location: React.PropTypes.object, - router: React.PropTypes.object.isRequired - }, - updateLocation: function (pathname, queryUpdate) { - if (pathname === undefined) { - pathname = this.context.location.pathname; - } - var query = this.context.location.query; - if (queryUpdate !== undefined) { - for (var i in queryUpdate) { - if (queryUpdate.hasOwnProperty(i)) { - query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. - } - } - } - this.context.router.replace({pathname, query}); - }, - getQuery: function () { - // For whatever reason, react-router always returns the same object, which makes comparing - // the current props with nextProps impossible. As a workaround, we just clone the query object. - return _.clone(this.context.location.query); - } -}; - export var Splitter = React.createClass({ getDefaultProps: function () { return { @@ -133,14 +108,55 @@ export var Splitter = React.createClass({ } }); -export const ToggleComponent = (props) => - <div - className={"btn " + (props.checked ? "btn-primary" : "btn-default")} - onClick={props.onToggleChanged}> - <span><i className={"fa " + (props.checked ? "fa-check-square-o" : "fa-square-o")}></i> {props.name}</span> - </div> +export const ToggleButton = (props) => + <div className="input-group toggle-btn"> + <div + className={"btn " + (props.checked ? "btn-primary" : "btn-default")} + onClick={props.onToggleChanged}> + <span className={"fa " + (props.checked ? "fa-check-square-o" : "fa-square-o")}> {props.name}</span> + </div> + </div>; + +ToggleButton.propTypes = { + name: React.PropTypes.string.isRequired, + onToggleChanged: React.PropTypes.func.isRequired +}; + +export class ToggleInputButton extends React.Component { + constructor(props) { + super(props); + this.state = {txt: props.txt}; + } -ToggleComponent.propTypes = { + render() { + return ( + <div className="input-group toggle-input-btn"> + <span + className="input-group-btn" + onClick={() => this.props.onToggleChanged(this.state.txt)}> + <div className={"btn " + (this.props.checked ? "btn-primary" : "btn-default")}> + <span className={"fa " + (this.props.checked ? "fa-check-square-o" : "fa-square-o")}/> + {this.props.name} + </div> + </span> + <input + className="form-control" + placeholder={this.props.placeholder} + disabled={this.props.checked} + value={this.state.txt} + type={this.props.inputType} + onChange={e => this.setState({txt: e.target.value})} + onKeyDown={e => {if (e.keyCode === Key.ENTER) this.props.onToggleChanged(this.state.txt); e.stopPropagation()}}/> + </div> + ); + } +} + +ToggleInputButton.propTypes = { name: React.PropTypes.string.isRequired, + txt: React.PropTypes.string.isRequired, onToggleChanged: React.PropTypes.func.isRequired -}
\ No newline at end of file +}; + + + diff --git a/web/src/js/components/eventlog.js b/web/src/js/components/eventlog.js index d1b23ace..6e4f9096 100644 --- a/web/src/js/components/eventlog.js +++ b/web/src/js/components/eventlog.js @@ -1,7 +1,6 @@ import React from "react" import ReactDOM from "react-dom" import shallowEqual from "shallowequal" -import {Router} from "./common.js" import {Query} from "../actions.js" import AutoScroll from "./helpers/AutoScroll"; import {calcVScroll} from "./helpers/VirtualScroll" @@ -144,7 +143,6 @@ function ToggleFilter ({ name, active, toggleLevel }) { const AutoScrollEventLog = AutoScroll(EventLogContents); var EventLog = React.createClass({ - mixins: [Router], getInitialState() { return { filter: { @@ -157,7 +155,7 @@ var EventLog = React.createClass({ close() { var d = {}; d[Query.SHOW_EVENTLOG] = undefined; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, toggleLevel(level) { var filter = _.extend({}, this.state.filter); diff --git a/web/src/js/components/flowview/contentview.js b/web/src/js/components/flowview/contentview.js index 2743eec3..cbac9a75 100644 --- a/web/src/js/components/flowview/contentview.js +++ b/web/src/js/components/flowview/contentview.js @@ -4,11 +4,15 @@ import _ from "lodash"; import {MessageUtils} from "../../flow/utils.js"; import {formatSize} from "../../utils.js"; -var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; var ViewImage = React.createClass({ + propTypes: { + flow: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + }, statics: { + regex: /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i, matches: function (message) { - return image_regex.test(MessageUtils.getContentType(message)); + return ViewImage.regex.test(MessageUtils.getContentType(message)); } }, render: function () { @@ -19,7 +23,11 @@ var ViewImage = React.createClass({ } }); -var RawMixin = { +var ContentLoader = React.createClass({ + propTypes: { + flow: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + }, getInitialState: function () { return { content: undefined, @@ -66,41 +74,54 @@ var RawMixin = { <i className="fa fa-spinner fa-spin"></i> </div>; } - return this.renderContent(); + return React.cloneElement(this.props.children, { + content: this.state.content + }) } -}; +}); var ViewRaw = React.createClass({ - mixins: [RawMixin], + propTypes: { + content: React.PropTypes.string.isRequired, + }, statics: { + textView: true, matches: function (message) { return true; } }, - renderContent: function () { - return <pre>{this.state.content}</pre>; + render: function () { + return <pre>{this.props.content}</pre>; } }); -var json_regex = /^application\/json$/i; var ViewJSON = React.createClass({ - mixins: [RawMixin], + propTypes: { + content: React.PropTypes.string.isRequired, + }, statics: { + textView: true, + regex: /^application\/json$/i, matches: function (message) { - return json_regex.test(MessageUtils.getContentType(message)); + return ViewJSON.regex.test(MessageUtils.getContentType(message)); } }, - renderContent: function () { - var json = this.state.content; + render: function () { + var json = this.props.content; try { json = JSON.stringify(JSON.parse(json), null, 2); } catch (e) { + // @noop } return <pre>{json}</pre>; } }); var ViewAuto = React.createClass({ + propTypes: { + message: React.PropTypes.object.isRequired, + flow: React.PropTypes.object.isRequired, + }, statics: { matches: function () { return false; // don't match itself @@ -115,14 +136,18 @@ var ViewAuto = React.createClass({ } }, render: function () { + var { message, flow } = this.props var View = ViewAuto.findView(this.props.message); - return <View {...this.props}/>; + if (View.textView) { + return <ContentLoader message={message} flow={flow}><View content="" /></ContentLoader> + } else { + return <View message={message} flow={flow} /> + } } }); var all = [ViewAuto, ViewImage, ViewJSON, ViewRaw]; - var ContentEmpty = React.createClass({ render: function () { var message_name = this.props.flow.request === this.props.message ? "request" : "response"; @@ -210,6 +235,7 @@ var ContentView = React.createClass({ } }, render: function () { + var { flow, message } = this.props var message = this.props.message; if (message.contentLength === 0) { return <ContentEmpty {...this.props}/>; @@ -222,7 +248,11 @@ var ContentView = React.createClass({ var downloadUrl = MessageUtils.getContentURL(this.props.flow, message); return <div> - <this.state.View {...this.props} /> + {this.state.View.textView ? ( + <ContentLoader flow={flow} message={message}><this.state.View content="" /></ContentLoader> + ) : ( + <this.state.View flow={flow} message={message} /> + )} <div className="view-options text-center"> <ViewSelector selectView={this.selectView} active={this.state.View} message={message}/> @@ -234,4 +264,4 @@ var ContentView = React.createClass({ } }); -export default ContentView;
\ No newline at end of file +export default ContentView; diff --git a/web/src/js/components/flowview/index.js b/web/src/js/components/flowview/index.js index 47531f58..6f4f7395 100644 --- a/web/src/js/components/flowview/index.js +++ b/web/src/js/components/flowview/index.js @@ -1,6 +1,5 @@ import React from "react"; -import {Router, StickyHeadMixin} from "../common.js" import Nav from "./nav.js"; import {Request, Response, Error} from "./messages.js"; import Details from "./details.js"; @@ -15,7 +14,6 @@ var allTabs = { }; var FlowView = React.createClass({ - mixins: [StickyHeadMixin, Router], getInitialState: function () { return { prompt: false @@ -39,7 +37,7 @@ var FlowView = React.createClass({ this.selectTab(tabs[nextIndex]); }, selectTab: function (panel) { - this.updateLocation(`/flows/${this.props.flow.id}/${panel}`); + this.props.updateLocation(`/flows/${this.props.flow.id}/${panel}`); }, promptEdit: function () { var options; @@ -114,4 +112,4 @@ var FlowView = React.createClass({ } }); -export default FlowView;
\ No newline at end of file +export default FlowView; diff --git a/web/src/js/components/footer.js b/web/src/js/components/footer.js index e2d96288..8fe1081b 100644 --- a/web/src/js/components/footer.js +++ b/web/src/js/components/footer.js @@ -1,4 +1,5 @@ import React from "react"; +import {formatSize} from "../utils.js" import {SettingsState} from "./common.js"; Footer.propTypes = { @@ -6,7 +7,7 @@ Footer.propTypes = { }; export default function Footer({ settings }) { - const {mode, intercept} = settings; + const {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickyauth, stickycookie, stream} = settings; return ( <footer> {mode && mode != "regular" && ( @@ -15,6 +16,35 @@ export default function Footer({ settings }) { {intercept && ( <span className="label label-success">Intercept: {intercept}</span> )} + {showhost && ( + <span className="label label-success">showhost</span> + )} + {no_upstream_cert && ( + <span className="label label-success">no-upstream-cert</span> + )} + {rawtcp && ( + <span className="label label-success">raw-tcp</span> + )} + {!http2 && ( + <span className="label label-success">no-http2</span> + )} + {anticache && ( + <span className="label label-success">anticache</span> + )} + {anticomp && ( + <span className="label label-success">anticomp</span> + )} + {stickyauth && ( + <span className="label label-success">stickyauth: {stickyauth}</span> + )} + {stickycookie && ( + <span className="label label-success">stickycookie: {stickycookie}</span> + )} + {stream && ( + <span className="label label-success">stream: {formatSize(stream)}</span> + )} + + </footer> ); } diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js index 226cb61f..643659c3 100644 --- a/web/src/js/components/header.js +++ b/web/src/js/components/header.js @@ -4,9 +4,10 @@ import $ from "jquery"; import Filt from "../filt/filt.js"; import {Key} from "../utils.js"; -import {Router, ToggleComponent} from "./common.js"; +import {ToggleInputButton, ToggleButton} from "./common.js"; import {SettingsActions, FlowActions} from "../actions.js"; import {Query} from "../actions.js"; +import {SettingsState} from "./common.js"; var FilterDocs = React.createClass({ statics: { @@ -161,7 +162,6 @@ var FilterInput = React.createClass({ }); export var MainMenu = React.createClass({ - mixins: [Router], propTypes: { settings: React.PropTypes.object.isRequired, }, @@ -172,19 +172,19 @@ export var MainMenu = React.createClass({ onSearchChange: function (val) { var d = {}; d[Query.SEARCH] = val; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, onHighlightChange: function (val) { var d = {}; d[Query.HIGHLIGHT] = val; - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); }, onInterceptChange: function (val) { SettingsActions.update({intercept: val}); }, render: function () { - var search = this.getQuery()[Query.SEARCH] || ""; - var highlight = this.getQuery()[Query.HIGHLIGHT] || ""; + var search = this.props.query[Query.SEARCH] || ""; + var highlight = this.props.query[Query.HIGHLIGHT] || ""; var intercept = this.props.settings.intercept || ""; return ( @@ -224,72 +224,88 @@ var ViewMenu = React.createClass({ title: "View", route: "flows" }, - mixins: [Router], toggleEventLog: function () { var d = {}; - if (this.getQuery()[Query.SHOW_EVENTLOG]) { + if (this.props.query[Query.SHOW_EVENTLOG]) { d[Query.SHOW_EVENTLOG] = undefined; } else { d[Query.SHOW_EVENTLOG] = "t"; // any non-false value will do it, keep it short } - this.updateLocation(undefined, d); + this.props.updateLocation(undefined, d); console.log('toggleevent'); }, render: function () { - var showEventLog = this.getQuery()[Query.SHOW_EVENTLOG]; + var showEventLog = this.props.query[Query.SHOW_EVENTLOG]; return ( - <div> - <ToggleComponent - checked={showEventLog} - name = "Show Eventlog" - onToggleChanged={this.toggleEventLog}/> - </div> + <div> + <div className="menu-row"> + <ToggleButton + checked={showEventLog} + name = "Show Eventlog" + onToggleChanged={this.toggleEventLog}/> + </div> + <div className="clearfix"></div> + </div> ); } }); - -class OptionMenu extends React.Component{ - static title = "Options"; - constructor(props){ - super(props); - this.state = { - options : - [ - {name: "--host", checked: true}, - {name: "--no-upstream-cert", checked: false}, - {name: "--http2", checked: false}, - {name: "--anticache", checked: false}, - {name: "--anticomp", checked: false}, - {name: "--stickycookie", checked: true}, - {name: "--stickyauth", checked: false}, - {name: "--stream", checked: false} - ] - } - } - setOption(entry){ - console.log(entry.name);//TODO: get options from outside and remove state - entry.checked = !entry.checked; - this.setState({options: this.state.options}); - } - render() { - return ( +export const OptionMenu = (props) => { + const {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickycookie, stickyauth, stream} = props.settings; + return ( <div> - {this.state.options.map((entry, i) => { - return ( - <ToggleComponent - key={i} - checked={entry.checked} - name = {entry.name} - onToggleChanged={() => this.setOption(entry)}/> - ); - })} + <div className="menu-row"> + <ToggleButton name="showhost" + checked={showhost} + onToggleChanged={() => SettingsActions.update({showhost: !showhost})} + /> + <ToggleButton name="no_upstream_cert" + checked={no_upstream_cert} + onToggleChanged={() => SettingsActions.update({no_upstream_cert: !no_upstream_cert})} + /> + <ToggleButton name="rawtcp" + checked={rawtcp} + onToggleChanged={() => SettingsActions.update({rawtcp: !rawtcp})} + /> + <ToggleButton name="http2" + checked={http2} + onToggleChanged={() => SettingsActions.update({http2: !http2})} + /> + <ToggleButton name="anticache" + checked={anticache} + onToggleChanged={() => SettingsActions.update({anticache: !anticache})} + /> + <ToggleButton name="anticomp" + checked={anticomp} + onToggleChanged={() => SettingsActions.update({anticomp: !anticomp})} + /> + <ToggleInputButton name="stickyauth" placeholder="Sticky auth filter" + checked={Boolean(stickyauth)} + txt={stickyauth || ""} + onToggleChanged={txt => SettingsActions.update({stickyauth: (!stickyauth ? txt : null)})} + /> + <ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter" + checked={Boolean(stickycookie)} + txt={stickycookie || ""} + onToggleChanged={txt => SettingsActions.update({stickycookie: (!stickycookie ? txt : null)})} + /> + <ToggleInputButton name="stream" placeholder="stream..." + checked={Boolean(stream)} + txt={stream || ""} + inputType = "number" + onToggleChanged={txt => SettingsActions.update({stream: (!stream ? txt : null)})} + /> + </div> + <div className="clearfix"/> </div> - ); - } -} + ); +}; +OptionMenu.title = "Options"; +OptionMenu.propTypes = { + settings: React.PropTypes.object.isRequired +}; var ReportsMenu = React.createClass({ statics: { @@ -391,7 +407,6 @@ var header_entries = [MainMenu, ViewMenu, OptionMenu /*, ReportsMenu */]; export var Header = React.createClass({ - mixins: [Router], propTypes: { settings: React.PropTypes.object.isRequired, }, @@ -402,7 +417,7 @@ export var Header = React.createClass({ }, handleClick: function (active, e) { e.preventDefault(); - this.updateLocation(active.route); + this.props.updateLocation(active.route); this.setState({active: active}); }, render: function () { @@ -430,7 +445,11 @@ export var Header = React.createClass({ {header} </nav> <div className="menu"> - <this.state.active ref="active" settings={this.props.settings}/> + <this.state.active + settings={this.props.settings} + updateLocation={this.props.updateLocation} + query={this.props.query} + /> </div> </header> ); diff --git a/web/src/js/components/mainview.js b/web/src/js/components/mainview.js index 87c0c4bd..964e82db 100644 --- a/web/src/js/components/mainview.js +++ b/web/src/js/components/mainview.js @@ -5,12 +5,11 @@ import {Query} from "../actions.js"; import {Key} from "../utils.js"; import {StoreView} from "../store/view.js"; import Filt from "../filt/filt.js"; -import { Router, Splitter} from "./common.js" +import {Splitter} from "./common.js" import FlowTable from "./flowtable.js"; import FlowView from "./flowview/index.js"; var MainView = React.createClass({ - mixins: [Router], contextTypes: { flowStore: React.PropTypes.object.isRequired, }, @@ -41,9 +40,9 @@ var MainView = React.createClass({ }, getViewFilt: function () { try { - var filtStr = this.getQuery()[Query.SEARCH]; + var filtStr = this.props.query[Query.SEARCH]; var filt = filtStr ? Filt.parse(filtStr) : () => true; - var highlightStr = this.getQuery()[Query.HIGHLIGHT]; + var highlightStr = this.props.query[Query.HIGHLIGHT]; var highlight = highlightStr ? Filt.parse(highlightStr) : () => false; } catch (e) { console.error("Error when processing filter: " + e); @@ -94,10 +93,10 @@ var MainView = React.createClass({ selectFlow: function (flow) { if (flow) { var tab = this.props.routeParams.detailTab || "request"; - this.updateLocation(`/flows/${flow.id}/${tab}`); + this.props.updateLocation(`/flows/${flow.id}/${tab}`); this.refs.flowTable.scrollIntoView(flow); } else { - this.updateLocation("/flows"); + this.props.updateLocation("/flows"); } }, selectFlowRelative: function (shift) { @@ -225,6 +224,8 @@ var MainView = React.createClass({ key="flowDetails" ref="flowDetails" tab={this.props.routeParams.detailTab} + query={this.props.query} + updateLocation={this.props.updateLocation} flow={selected}/> ]; } else { diff --git a/web/src/js/components/proxyapp.js b/web/src/js/components/proxyapp.js index d17a1522..f47c5bb4 100644 --- a/web/src/js/components/proxyapp.js +++ b/web/src/js/components/proxyapp.js @@ -2,7 +2,7 @@ import React from "react"; import ReactDOM from "react-dom"; import _ from "lodash"; -import {Router, Splitter} from "./common.js" +import {Splitter} from "./common.js" import MainView from "./mainview.js"; import Footer from "./footer.js"; import {Header, MainMenu} from "./header.js"; @@ -21,13 +21,34 @@ var Reports = React.createClass({ var ProxyAppMain = React.createClass({ - mixins: [Router], childContextTypes: { flowStore: React.PropTypes.object.isRequired, eventStore: React.PropTypes.object.isRequired, returnFocus: React.PropTypes.func.isRequired, location: React.PropTypes.object.isRequired, }, + contextTypes: { + router: React.PropTypes.object.isRequired + }, + updateLocation: function (pathname, queryUpdate) { + if (pathname === undefined) { + pathname = this.props.location.pathname; + } + var query = this.props.location.query; + if (queryUpdate !== undefined) { + for (var i in queryUpdate) { + if (queryUpdate.hasOwnProperty(i)) { + query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. + } + } + } + this.context.router.replace({pathname, query}); + }, + getQuery: function () { + // For whatever reason, react-router always returns the same object, which makes comparing + // the current props with nextProps impossible. As a workaround, we just clone the query object. + return _.clone(this.props.location.query); + }, componentDidMount: function () { this.focus(); this.settingsStore.addListener("recalculate", this.onSettingsChange); @@ -97,23 +118,23 @@ var ProxyAppMain = React.createClass({ e.preventDefault(); }, render: function () { + var query = this.getQuery(); var eventlog; if (this.props.location.query[Query.SHOW_EVENTLOG]) { eventlog = [ <Splitter key="splitter" axis="y"/>, - <EventLog key="eventlog"/> + <EventLog key="eventlog" updateLocation={this.updateLocation}/> ]; } else { eventlog = null; } - var children = React.cloneElement( - this.props.children, - { ref: "view", location: this.props.location } - ); return ( <div id="container" tabIndex="0" onKeyDown={this.onKeydown}> - <Header ref="header" settings={this.state.settings}/> - {children} + <Header ref="header" settings={this.state.settings} updateLocation={this.updateLocation} query={query} /> + {React.cloneElement( + this.props.children, + { ref: "view", location: this.props.location , updateLocation: this.updateLocation, query } + )} {eventlog} <Footer settings={this.state.settings}/> </div> @@ -125,12 +146,12 @@ var ProxyAppMain = React.createClass({ import { Route, Router as ReactRouter, hashHistory, Redirect} from "react-router"; export var app = ( -<ReactRouter history={hashHistory}> - <Redirect from="/" to="/flows" /> - <Route path="/" component={ProxyAppMain}> - <Route path="flows" component={MainView}/> - <Route path="flows/:flowId/:detailTab" component={MainView}/> - <Route path="reports" component={Reports}/> - </Route> -</ReactRouter> -);
\ No newline at end of file + <ReactRouter history={hashHistory}> + <Redirect from="/" to="/flows" /> + <Route path="/" component={ProxyAppMain}> + <Route path="flows" component={MainView}/> + <Route path="flows/:flowId/:detailTab" component={MainView}/> + <Route path="reports" component={Reports}/> + </Route> + </ReactRouter> +); |