aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml16
-rw-r--r--.gitignore1
-rw-r--r--README.rst3
-rw-r--r--docs/.gitignore1
-rw-r--r--docs/README.md5
-rw-r--r--docs/modd.conf5
-rw-r--r--docs/package.json6
-rw-r--r--docs/src/assets/badge.scss (renamed from docs/style/badge.scss)0
-rw-r--r--docs/src/assets/bulma/base/_all.sass5
-rw-r--r--docs/src/assets/bulma/base/generic.sass142
-rw-r--r--docs/src/assets/bulma/base/helpers.sass281
-rw-r--r--docs/src/assets/bulma/base/minireset.sass79
-rw-r--r--docs/src/assets/bulma/components/_all.sass15
-rw-r--r--docs/src/assets/bulma/components/breadcrumb.sass75
-rw-r--r--docs/src/assets/bulma/components/card.sass79
-rw-r--r--docs/src/assets/bulma/components/dropdown.sass81
-rw-r--r--docs/src/assets/bulma/components/level.sass77
-rw-r--r--docs/src/assets/bulma/components/list.sass39
-rw-r--r--docs/src/assets/bulma/components/media.sass50
-rw-r--r--docs/src/assets/bulma/components/menu.sass57
-rw-r--r--docs/src/assets/bulma/components/message.sass99
-rw-r--r--docs/src/assets/bulma/components/modal.sass113
-rw-r--r--docs/src/assets/bulma/components/navbar.sass443
-rw-r--r--docs/src/assets/bulma/components/pagination.sass150
-rw-r--r--docs/src/assets/bulma/components/panel.sass119
-rw-r--r--docs/src/assets/bulma/components/tabs.sass151
-rw-r--r--docs/src/assets/bulma/elements/_all.sass15
-rw-r--r--docs/src/assets/bulma/elements/box.sass24
-rw-r--r--docs/src/assets/bulma/elements/button.sass323
-rw-r--r--docs/src/assets/bulma/elements/container.sass24
-rw-r--r--docs/src/assets/bulma/elements/content.sass155
-rw-r--r--docs/src/assets/bulma/elements/form.sass1
-rw-r--r--docs/src/assets/bulma/elements/icon.sass21
-rw-r--r--docs/src/assets/bulma/elements/image.sass71
-rw-r--r--docs/src/assets/bulma/elements/notification.sass43
-rw-r--r--docs/src/assets/bulma/elements/other.sass39
-rw-r--r--docs/src/assets/bulma/elements/progress.sass67
-rw-r--r--docs/src/assets/bulma/elements/table.sass127
-rw-r--r--docs/src/assets/bulma/elements/tag.sass128
-rw-r--r--docs/src/assets/bulma/elements/title.sass70
-rw-r--r--docs/src/assets/bulma/form/_all.sass8
-rw-r--r--docs/src/assets/bulma/form/checkbox-radio.sass21
-rw-r--r--docs/src/assets/bulma/form/file.sass180
-rw-r--r--docs/src/assets/bulma/form/input-textarea.sass64
-rw-r--r--docs/src/assets/bulma/form/select.sass85
-rw-r--r--docs/src/assets/bulma/form/shared.sass55
-rw-r--r--docs/src/assets/bulma/form/tools.sass205
-rw-r--r--docs/src/assets/bulma/grid/_all.sass4
-rw-r--r--docs/src/assets/bulma/grid/columns.sass504
-rw-r--r--docs/src/assets/bulma/grid/tiles.sass34
-rw-r--r--docs/src/assets/bulma/layout/_all.sass5
-rw-r--r--docs/src/assets/bulma/layout/footer.sass9
-rw-r--r--docs/src/assets/bulma/layout/hero.sass144
-rw-r--r--docs/src/assets/bulma/layout/section.sass13
-rw-r--r--docs/src/assets/bulma/utilities/_all.sass8
-rw-r--r--docs/src/assets/bulma/utilities/animations.sass5
-rw-r--r--docs/src/assets/bulma/utilities/controls.sass50
-rw-r--r--docs/src/assets/bulma/utilities/derived-variables.sass106
-rw-r--r--docs/src/assets/bulma/utilities/functions.sass103
-rw-r--r--docs/src/assets/bulma/utilities/initial-variables.sass77
-rw-r--r--docs/src/assets/bulma/utilities/mixins.sass261
-rw-r--r--docs/src/assets/fa/_animated.scss (renamed from docs/style/fa/_animated.scss)0
-rw-r--r--docs/src/assets/fa/_bordered-pulled.scss (renamed from docs/style/fa/_bordered-pulled.scss)0
-rw-r--r--docs/src/assets/fa/_core.scss (renamed from docs/style/fa/_core.scss)0
-rw-r--r--docs/src/assets/fa/_fixed-width.scss (renamed from docs/style/fa/_fixed-width.scss)0
-rw-r--r--docs/src/assets/fa/_icons.scss (renamed from docs/style/fa/_icons.scss)0
-rw-r--r--docs/src/assets/fa/_larger.scss (renamed from docs/style/fa/_larger.scss)0
-rw-r--r--docs/src/assets/fa/_list.scss (renamed from docs/style/fa/_list.scss)0
-rw-r--r--docs/src/assets/fa/_mixins.scss (renamed from docs/style/fa/_mixins.scss)0
-rw-r--r--docs/src/assets/fa/_rotated-flipped.scss (renamed from docs/style/fa/_rotated-flipped.scss)0
-rw-r--r--docs/src/assets/fa/_screen-reader.scss (renamed from docs/style/fa/_screen-reader.scss)0
-rw-r--r--docs/src/assets/fa/_stacked.scss (renamed from docs/style/fa/_stacked.scss)0
-rw-r--r--docs/src/assets/fa/_variables.scss (renamed from docs/style/fa/_variables.scss)0
-rw-r--r--docs/src/assets/fa/fa-brands.scss (renamed from docs/style/fa/fa-brands.scss)0
-rw-r--r--docs/src/assets/fa/fa-regular.scss (renamed from docs/style/fa/fa-regular.scss)0
-rw-r--r--docs/src/assets/fa/fa-solid.scss (renamed from docs/style/fa/fa-solid.scss)0
-rw-r--r--docs/src/assets/fa/fontawesome.scss (renamed from docs/style/fa/fontawesome.scss)0
-rw-r--r--docs/src/assets/style.scss (renamed from docs/style/style.scss)15
-rw-r--r--docs/src/assets/syntax.css (renamed from docs/style/syntax.css)0
-rw-r--r--docs/src/content/addons-events.md16
-rw-r--r--docs/src/content/addons-scripting.md33
-rw-r--r--docs/src/content/concepts-certificates.md2
-rw-r--r--docs/src/content/concepts-commands.md10
-rw-r--r--docs/src/content/concepts-filters.md5
-rw-r--r--docs/src/content/concepts-protocols.md4
-rw-r--r--docs/src/content/tools-mitmdump.md10
-rw-r--r--docs/src/content/tute-clientreplay.md7
-rw-r--r--docs/src/themes/mitmproxydocs/layouts/partials/header.html7
-rw-r--r--docs/yarn.lock1203
-rw-r--r--examples/addons/events-http-specific.py42
-rw-r--r--examples/addons/events-tcp-specific.py25
-rw-r--r--examples/addons/events-websocket-specific.py36
-rw-r--r--examples/addons/events.py93
-rw-r--r--examples/complex/README.md1
-rw-r--r--examples/complex/block_dns_over_https.py177
-rw-r--r--examples/complex/nonblocking.py7
-rw-r--r--examples/complex/websocket_inject_message.py2
-rw-r--r--examples/simple/websocket_messages.py11
-rw-r--r--examples/simple/wsgi_flask_app.py6
-rw-r--r--mitmproxy/__init__.py8
-rw-r--r--mitmproxy/addons/browser.py2
-rw-r--r--mitmproxy/addons/onboardingapp/templates/index.html16
-rw-r--r--mitmproxy/addons/script.py13
-rw-r--r--mitmproxy/certs.py4
-rw-r--r--mitmproxy/command.py13
-rw-r--r--mitmproxy/net/check.py3
-rw-r--r--mitmproxy/net/http/headers.py8
-rw-r--r--mitmproxy/net/http/http1/assemble.py11
-rw-r--r--mitmproxy/net/http/http1/read.py34
-rw-r--r--mitmproxy/net/server_spec.py8
-rw-r--r--mitmproxy/net/tls.py78
-rw-r--r--mitmproxy/optmanager.py3
-rw-r--r--mitmproxy/proxy/config.py2
-rw-r--r--mitmproxy/proxy/protocol/base.py4
-rw-r--r--mitmproxy/proxy/protocol/http.py6
-rw-r--r--mitmproxy/proxy/protocol/http2.py46
-rw-r--r--mitmproxy/proxy/protocol/rawtcp.py11
-rw-r--r--mitmproxy/tools/_main.py12
-rw-r--r--mitmproxy/tools/cmdline.py2
-rw-r--r--mitmproxy/tools/console/commandexecutor.py5
-rw-r--r--mitmproxy/tools/console/common.py2
-rw-r--r--mitmproxy/tools/console/consoleaddons.py35
-rw-r--r--mitmproxy/tools/console/keymap.py2
-rw-r--r--mitmproxy/tools/web/master.py40
-rw-r--r--mitmproxy/tools/web/static/app.js4
-rw-r--r--mitmproxy/tools/web/webaddons.py45
-rw-r--r--mitmproxy/types.py6
-rw-r--r--pathod/language/actions.py2
-rw-r--r--release/README.md1
-rwxr-xr-xrelease/cibuild.py8
-rw-r--r--release/docker/README.md2
-rw-r--r--release/hooks/hook-pkg_resources.py7
-rw-r--r--setup.py16
-rw-r--r--test/mitmproxy/addons/test_script.py11
-rw-r--r--test/mitmproxy/data/addonscripts/configure.py21
-rw-r--r--test/mitmproxy/data/servercert/9da13359.042
-rw-r--r--test/mitmproxy/data/servercert/self-signed.pem87
-rw-r--r--test/mitmproxy/data/servercert/trusted-leaf.pem85
-rw-r--r--test/mitmproxy/data/servercert/trusted-root.pem88
-rw-r--r--test/mitmproxy/net/data/verificationcerts/9da13359.042
-rw-r--r--test/mitmproxy/net/data/verificationcerts/generate.py54
-rw-r--r--test/mitmproxy/net/data/verificationcerts/self-signed.crt41
-rw-r--r--test/mitmproxy/net/data/verificationcerts/self-signed.key54
-rw-r--r--test/mitmproxy/net/data/verificationcerts/trusted-leaf.crt39
-rw-r--r--test/mitmproxy/net/data/verificationcerts/trusted-leaf.key54
-rw-r--r--test/mitmproxy/net/data/verificationcerts/trusted-root.crt42
-rw-r--r--test/mitmproxy/net/data/verificationcerts/trusted-root.key54
-rw-r--r--test/mitmproxy/net/data/verificationcerts/trusted-root.srl2
-rw-r--r--test/mitmproxy/net/http/http1/test_assemble.py12
-rw-r--r--test/mitmproxy/net/http/http1/test_read.py5
-rw-r--r--test/mitmproxy/net/http/test_headers.py2
-rw-r--r--test/mitmproxy/net/test_check.py58
-rw-r--r--test/mitmproxy/net/test_tcp.py35
-rw-r--r--test/mitmproxy/net/test_tls.py2
-rw-r--r--test/mitmproxy/proxy/protocol/test_http2.py65
-rw-r--r--test/mitmproxy/test_command.py9
-rw-r--r--test/mitmproxy/test_connections.py4
-rw-r--r--test/mitmproxy/tools/console/test_keymap.py22
-rw-r--r--tox.ini2
-rw-r--r--web/README.md4
-rw-r--r--web/src/js/__tests__/components/Header/__snapshots__/OptionMenuSpec.js.snap2
-rw-r--r--web/src/js/components/Header/OptionMenu.jsx2
162 files changed, 6317 insertions, 1935 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c856523b..648292c8 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -2,10 +2,6 @@ name: CI
on: [push, pull_request]
-# We currently use Python 3.7 for most things:
-# - zstandard currently doesn't have 3.8 wheels,
-# - we need to upgrade cryptography from version 2.4, which also doesn't have wheels
-
env:
# Codecov
CODECOV_TOKEN: "0409bdfd-57a4-477d-a8af-f6172ef431d3"
@@ -57,9 +53,9 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
- python-version: '3.7'
+ python-version: '3.8'
- run: pip install tox
- - run: tox -e py37
+ - run: tox -e py38
# codecov's GitHub action only supports Linux. https://github.com/codecov/codecov-action/issues/7
# codecov's Python uploader has no github actions support yet. https://github.com/codecov/codecov-python/pull/214
- name: Extract branch name # https://stackoverflow.com/a/58035262/934719
@@ -90,7 +86,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
- python-version: '3.7'
+ python-version: '3.8'
- run: pip install tox
- run: tox -e cibuild -- build
- uses: actions/upload-artifact@master
@@ -155,10 +151,10 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
- python-version: '3.7'
+ python-version: '3.8'
- run: pip install tox
- run: |
- wget https://github.com/gohugoio/hugo/releases/download/v0.59.1/hugo_0.59.1_Linux-64bit.deb
+ wget https://github.com/gohugoio/hugo/releases/download/v0.65.3/hugo_extended_0.65.3_Linux-64bit.deb
sudo dpkg -i hugo*.deb
- run: tox -e docs
@@ -200,7 +196,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
- python-version: '3.7'
+ python-version: '3.8'
# artifacts must be downloaded individually, see https://github.com/actions/download-artifact/issues/6
- uses: actions/download-artifact@master
with:
diff --git a/.gitignore b/.gitignore
index 6b514131..0d5943d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@ dist/
mitmproxy/contrib/kaitaistruct/*.ksy
.pytest_cache
__pycache__
+.hypothesis/
# UI
diff --git a/README.rst b/README.rst
index cfcc3e7f..26881ff9 100644
--- a/README.rst
+++ b/README.rst
@@ -124,12 +124,10 @@ The following tools are required to build the mitmproxy docs:
- Hugo_
- modd_
-- yarn_
.. code-block:: bash
cd docs
- yarn
modd
@@ -191,7 +189,6 @@ with the following command:
.. _tox: https://tox.readthedocs.io/
.. _Hugo: https://gohugo.io/
.. _modd: https://github.com/cortesi/modd
-.. _yarn: https://yarnpkg.com/en/
.. _PEP8: https://www.python.org/dev/peps/pep-0008
.. _`Google Style Guide`: https://google.github.io/styleguide/pyguide.html
.. _StackOverflow: https://stackoverflow.com/questions/tagged/mitmproxy
diff --git a/docs/.gitignore b/docs/.gitignore
index 64b6fff6..1fb99949 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -2,3 +2,4 @@ generated/
src/public
node_modules
public
+src/resources/_gen/
diff --git a/docs/README.md b/docs/README.md
index a9ee1113..5c99fb39 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -5,7 +5,7 @@ This directory houses the mitmproxy documentation available at <https://docs.mit
## Quick Start
1. Install [hugo](https://gohugo.io/).
- 2. Windows users: Depending on your git settings, you may need to manually create a symlink from
+ 2. Windows users: Depending on your git settings, you may need to manually create a symlink from
/docs/src/examples to /examples.
3. Make sure the mitmproxy Python package is installed.
4. Run `./build-current` to generate the documentation source files in `./src/generated`.
@@ -17,7 +17,6 @@ Now you can run `hugo server -D` in ./src.
This is required to modify CSS files.
- 1. Install node, yarn, and [modd](https://github.com/cortesi/modd).
- 2. Run `yarn` in this directory to get node-sass.
+ 1. Install hugo extended version.
You can now run `modd` in this directory instead of running hugo directly.
diff --git a/docs/modd.conf b/docs/modd.conf
index ddadc11a..c4e0ffd7 100644
--- a/docs/modd.conf
+++ b/docs/modd.conf
@@ -1,8 +1,3 @@
{
daemon: cd src; hugo server -D
}
-
-style/** {
- # This is quite fast, so it can stay a prep rather than a daemon
- prep: ./node_modules/.bin/node-sass -o ./src/themes/mitmproxydocs/static/css ./style/style.scss --error-bell
-}
diff --git a/docs/package.json b/docs/package.json
deleted file mode 100644
index a950c88d..00000000
--- a/docs/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "dependencies": {
- "bulma": "^0.6.1",
- "node-sass": "^4.7.2"
- }
-}
diff --git a/docs/style/badge.scss b/docs/src/assets/badge.scss
index 8082f6c7..8082f6c7 100644
--- a/docs/style/badge.scss
+++ b/docs/src/assets/badge.scss
diff --git a/docs/src/assets/bulma/base/_all.sass b/docs/src/assets/bulma/base/_all.sass
new file mode 100644
index 00000000..e913d6ba
--- /dev/null
+++ b/docs/src/assets/bulma/base/_all.sass
@@ -0,0 +1,5 @@
+@charset "utf-8"
+
+@import "minireset.sass"
+@import "generic.sass"
+@import "helpers.sass"
diff --git a/docs/src/assets/bulma/base/generic.sass b/docs/src/assets/bulma/base/generic.sass
new file mode 100644
index 00000000..31eedfe9
--- /dev/null
+++ b/docs/src/assets/bulma/base/generic.sass
@@ -0,0 +1,142 @@
+$body-background-color: $scheme-main !default
+$body-size: 16px !default
+$body-min-width: 300px !default
+$body-rendering: optimizeLegibility !default
+$body-family: $family-primary !default
+$body-overflow-x: hidden !default
+$body-overflow-y: scroll !default
+
+$body-color: $text !default
+$body-font-size: 1em !default
+$body-weight: $weight-normal !default
+$body-line-height: 1.5 !default
+
+$code-family: $family-code !default
+$code-padding: 0.25em 0.5em 0.25em !default
+$code-weight: normal !default
+$code-size: 0.875em !default
+
+$small-font-size: 0.875em !default
+
+$hr-background-color: $background !default
+$hr-height: 2px !default
+$hr-margin: 1.5rem 0 !default
+
+$strong-color: $text-strong !default
+$strong-weight: $weight-bold !default
+
+$pre-font-size: 0.875em !default
+$pre-padding: 1.25rem 1.5rem !default
+$pre-code-font-size: 1em !default
+
+html
+ background-color: $body-background-color
+ font-size: $body-size
+ -moz-osx-font-smoothing: grayscale
+ -webkit-font-smoothing: antialiased
+ min-width: $body-min-width
+ overflow-x: $body-overflow-x
+ overflow-y: $body-overflow-y
+ text-rendering: $body-rendering
+ text-size-adjust: 100%
+
+article,
+aside,
+figure,
+footer,
+header,
+hgroup,
+section
+ display: block
+
+body,
+button,
+input,
+select,
+textarea
+ font-family: $body-family
+
+code,
+pre
+ -moz-osx-font-smoothing: auto
+ -webkit-font-smoothing: auto
+ font-family: $code-family
+
+body
+ color: $body-color
+ font-size: $body-font-size
+ font-weight: $body-weight
+ line-height: $body-line-height
+
+// Inline
+
+a
+ color: $link
+ cursor: pointer
+ text-decoration: none
+ strong
+ color: currentColor
+ &:hover
+ color: $link-hover
+
+code
+ background-color: $code-background
+ color: $code
+ font-size: $code-size
+ font-weight: $code-weight
+ padding: $code-padding
+
+hr
+ background-color: $hr-background-color
+ border: none
+ display: block
+ height: $hr-height
+ margin: $hr-margin
+
+img
+ height: auto
+ max-width: 100%
+
+input[type="checkbox"],
+input[type="radio"]
+ vertical-align: baseline
+
+small
+ font-size: $small-font-size
+
+span
+ font-style: inherit
+ font-weight: inherit
+
+strong
+ color: $strong-color
+ font-weight: $strong-weight
+
+// Block
+
+fieldset
+ border: none
+
+pre
+ +overflow-touch
+ background-color: $pre-background
+ color: $pre
+ font-size: $pre-font-size
+ overflow-x: auto
+ padding: $pre-padding
+ white-space: pre
+ word-wrap: normal
+ code
+ background-color: transparent
+ color: currentColor
+ font-size: $pre-code-font-size
+ padding: 0
+
+table
+ td,
+ th
+ vertical-align: top
+ &:not([align])
+ text-align: left
+ th
+ color: $text-strong
diff --git a/docs/src/assets/bulma/base/helpers.sass b/docs/src/assets/bulma/base/helpers.sass
new file mode 100644
index 00000000..bbb489dd
--- /dev/null
+++ b/docs/src/assets/bulma/base/helpers.sass
@@ -0,0 +1,281 @@
+// Float
+
+.is-clearfix
+ +clearfix
+
+.is-pulled-left
+ float: left !important
+
+.is-pulled-right
+ float: right !important
+
+// Overflow
+
+.is-clipped
+ overflow: hidden !important
+
+// Overlay
+
+.is-overlay
+ @extend %overlay
+
+// Typography
+
+=typography-size($target:'')
+ @each $size in $sizes
+ $i: index($sizes, $size)
+ .is-size-#{$i}#{if($target == '', '', '-' + $target)}
+ font-size: $size !important
+
++typography-size()
+
++mobile
+ +typography-size('mobile')
+
++tablet
+ +typography-size('tablet')
+
++touch
+ +typography-size('touch')
+
++desktop
+ +typography-size('desktop')
+
++widescreen
+ +typography-size('widescreen')
+
++fullhd
+ +typography-size('fullhd')
+
+$alignments: ('centered': 'center', 'justified': 'justify', 'left': 'left', 'right': 'right')
+
+@each $alignment, $text-align in $alignments
+ .has-text-#{$alignment}
+ text-align: #{$text-align} !important
+
+@each $alignment, $text-align in $alignments
+ +mobile
+ .has-text-#{$alignment}-mobile
+ text-align: #{$text-align} !important
+ +tablet
+ .has-text-#{$alignment}-tablet
+ text-align: #{$text-align} !important
+ +tablet-only
+ .has-text-#{$alignment}-tablet-only
+ text-align: #{$text-align} !important
+ +touch
+ .has-text-#{$alignment}-touch
+ text-align: #{$text-align} !important
+ +desktop
+ .has-text-#{$alignment}-desktop
+ text-align: #{$text-align} !important
+ +desktop-only
+ .has-text-#{$alignment}-desktop-only
+ text-align: #{$text-align} !important
+ +widescreen
+ .has-text-#{$alignment}-widescreen
+ text-align: #{$text-align} !important
+ +widescreen-only
+ .has-text-#{$alignment}-widescreen-only
+ text-align: #{$text-align} !important
+ +fullhd
+ .has-text-#{$alignment}-fullhd
+ text-align: #{$text-align} !important
+
+.is-capitalized
+ text-transform: capitalize !important
+
+.is-lowercase
+ text-transform: lowercase !important
+
+.is-uppercase
+ text-transform: uppercase !important
+
+.is-italic
+ font-style: italic !important
+
+@each $name, $pair in $colors
+ $color: nth($pair, 1)
+ .has-text-#{$name}
+ color: $color !important
+ a.has-text-#{$name}
+ &:hover,
+ &:focus
+ color: darken($color, 10%) !important
+ .has-background-#{$name}
+ background-color: $color !important
+
+@each $name, $shade in $shades
+ .has-text-#{$name}
+ color: $shade !important
+ .has-background-#{$name}
+ background-color: $shade !important
+
+.has-text-weight-light
+ font-weight: $weight-light !important
+.has-text-weight-normal
+ font-weight: $weight-normal !important
+.has-text-weight-medium
+ font-weight: $weight-medium !important
+.has-text-weight-semibold
+ font-weight: $weight-semibold !important
+.has-text-weight-bold
+ font-weight: $weight-bold !important
+
+.is-family-primary
+ font-family: $family-primary !important
+
+.is-family-secondary
+ font-family: $family-secondary !important
+
+.is-family-sans-serif
+ font-family: $family-sans-serif !important
+
+.is-family-monospace
+ font-family: $family-monospace !important
+
+.is-family-code
+ font-family: $family-code !important
+
+// Visibility
+
+$displays: 'block' 'flex' 'inline' 'inline-block' 'inline-flex'
+
+@each $display in $displays
+ .is-#{$display}
+ display: #{$display} !important
+ +mobile
+ .is-#{$display}-mobile
+ display: #{$display} !important
+ +tablet
+ .is-#{$display}-tablet
+ display: #{$display} !important
+ +tablet-only
+ .is-#{$display}-tablet-only
+ display: #{$display} !important
+ +touch
+ .is-#{$display}-touch
+ display: #{$display} !important
+ +desktop
+ .is-#{$display}-desktop
+ display: #{$display} !important
+ +desktop-only
+ .is-#{$display}-desktop-only
+ display: #{$display} !important
+ +widescreen
+ .is-#{$display}-widescreen
+ display: #{$display} !important
+ +widescreen-only
+ .is-#{$display}-widescreen-only
+ display: #{$display} !important
+ +fullhd
+ .is-#{$display}-fullhd
+ display: #{$display} !important
+
+.is-hidden
+ display: none !important
+
+.is-sr-only
+ border: none !important
+ clip: rect(0, 0, 0, 0) !important
+ height: 0.01em !important
+ overflow: hidden !important
+ padding: 0 !important
+ position: absolute !important
+ white-space: nowrap !important
+ width: 0.01em !important
+
++mobile
+ .is-hidden-mobile
+ display: none !important
+
++tablet
+ .is-hidden-tablet
+ display: none !important
+
++tablet-only
+ .is-hidden-tablet-only
+ display: none !important
+
++touch
+ .is-hidden-touch
+ display: none !important
+
++desktop
+ .is-hidden-desktop
+ display: none !important
+
++desktop-only
+ .is-hidden-desktop-only
+ display: none !important
+
++widescreen
+ .is-hidden-widescreen
+ display: none !important
+
++widescreen-only
+ .is-hidden-widescreen-only
+ display: none !important
+
++fullhd
+ .is-hidden-fullhd
+ display: none !important
+
+.is-invisible
+ visibility: hidden !important
+
++mobile
+ .is-invisible-mobile
+ visibility: hidden !important
+
++tablet
+ .is-invisible-tablet
+ visibility: hidden !important
+
++tablet-only
+ .is-invisible-tablet-only
+ visibility: hidden !important
+
++touch
+ .is-invisible-touch
+ visibility: hidden !important
+
++desktop
+ .is-invisible-desktop
+ visibility: hidden !important
+
++desktop-only
+ .is-invisible-desktop-only
+ visibility: hidden !important
+
++widescreen
+ .is-invisible-widescreen
+ visibility: hidden !important
+
++widescreen-only
+ .is-invisible-widescreen-only
+ visibility: hidden !important
+
++fullhd
+ .is-invisible-fullhd
+ visibility: hidden !important
+
+// Other
+
+.is-marginless
+ margin: 0 !important
+
+.is-paddingless
+ padding: 0 !important
+
+.is-radiusless
+ border-radius: 0 !important
+
+.is-shadowless
+ box-shadow: none !important
+
+.is-unselectable
+ @extend %unselectable
+
+.is-relative
+ position: relative !important
diff --git a/docs/src/assets/bulma/base/minireset.sass b/docs/src/assets/bulma/base/minireset.sass
new file mode 100644
index 00000000..c5657ebd
--- /dev/null
+++ b/docs/src/assets/bulma/base/minireset.sass
@@ -0,0 +1,79 @@
+/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */
+// Blocks
+html,
+body,
+p,
+ol,
+ul,
+li,
+dl,
+dt,
+dd,
+blockquote,
+figure,
+fieldset,
+legend,
+textarea,
+pre,
+iframe,
+hr,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6
+ margin: 0
+ padding: 0
+
+// Headings
+h1,
+h2,
+h3,
+h4,
+h5,
+h6
+ font-size: 100%
+ font-weight: normal
+
+// List
+ul
+ list-style: none
+
+// Form
+button,
+input,
+select,
+textarea
+ margin: 0
+
+// Box sizing
+html
+ box-sizing: border-box
+
+*
+ &,
+ &::before,
+ &::after
+ box-sizing: inherit
+
+// Media
+img,
+video
+ height: auto
+ max-width: 100%
+
+// Iframe
+iframe
+ border: 0
+
+// Table
+table
+ border-collapse: collapse
+ border-spacing: 0
+
+td,
+th
+ padding: 0
+ &:not([align])
+ text-align: left
diff --git a/docs/src/assets/bulma/components/_all.sass b/docs/src/assets/bulma/components/_all.sass
new file mode 100644
index 00000000..88fd45c5
--- /dev/null
+++ b/docs/src/assets/bulma/components/_all.sass
@@ -0,0 +1,15 @@
+@charset "utf-8"
+
+@import "breadcrumb.sass"
+@import "card.sass"
+@import "dropdown.sass"
+@import "level.sass"
+@import "list.sass"
+@import "media.sass"
+@import "menu.sass"
+@import "message.sass"
+@import "modal.sass"
+@import "navbar.sass"
+@import "pagination.sass"
+@import "panel.sass"
+@import "tabs.sass"
diff --git a/docs/src/assets/bulma/components/breadcrumb.sass b/docs/src/assets/bulma/components/breadcrumb.sass
new file mode 100644
index 00000000..3d7f4eb3
--- /dev/null
+++ b/docs/src/assets/bulma/components/breadcrumb.sass
@@ -0,0 +1,75 @@
+$breadcrumb-item-color: $link !default
+$breadcrumb-item-hover-color: $link-hover !default
+$breadcrumb-item-active-color: $text-strong !default
+
+$breadcrumb-item-padding-vertical: 0 !default
+$breadcrumb-item-padding-horizontal: 0.75em !default
+
+$breadcrumb-item-separator-color: $border-hover !default
+
+.breadcrumb
+ @extend %block
+ @extend %unselectable
+ font-size: $size-normal
+ white-space: nowrap
+ a
+ align-items: center
+ color: $breadcrumb-item-color
+ display: flex
+ justify-content: center
+ padding: $breadcrumb-item-padding-vertical $breadcrumb-item-padding-horizontal
+ &:hover
+ color: $breadcrumb-item-hover-color
+ li
+ align-items: center
+ display: flex
+ &:first-child a
+ padding-left: 0
+ &.is-active
+ a
+ color: $breadcrumb-item-active-color
+ cursor: default
+ pointer-events: none
+ & + li::before
+ color: $breadcrumb-item-separator-color
+ content: "\0002f"
+ ul,
+ ol
+ align-items: flex-start
+ display: flex
+ flex-wrap: wrap
+ justify-content: flex-start
+ .icon
+ &:first-child
+ margin-right: 0.5em
+ &:last-child
+ margin-left: 0.5em
+ // Alignment
+ &.is-centered
+ ol,
+ ul
+ justify-content: center
+ &.is-right
+ ol,
+ ul
+ justify-content: flex-end
+ // Sizes
+ &.is-small
+ font-size: $size-small
+ &.is-medium
+ font-size: $size-medium
+ &.is-large
+ font-size: $size-large
+ // Styles
+ &.has-arrow-separator
+ li + li::before
+ content: "\02192"
+ &.has-bullet-separator
+ li + li::before
+ content: "\02022"
+ &.has-dot-separator
+ li + li::before
+ content: "\000b7"
+ &.has-succeeds-separator
+ li + li::before
+ content: "\0227B"
diff --git a/docs/src/assets/bulma/components/card.sass b/docs/src/assets/bulma/components/card.sass
new file mode 100644
index 00000000..3cdf0008
--- /dev/null
+++ b/docs/src/assets/bulma/components/card.sass
@@ -0,0 +1,79 @@
+$card-color: $text !default
+$card-background-color: $scheme-main !default
+$card-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default
+
+$card-header-background-color: transparent !default
+$card-header-color: $text-strong !default
+$card-header-padding: 0.75rem 1rem !default
+$card-header-shadow: 0 0.125em 0.25em rgba($scheme-invert, 0.1) !default
+$card-header-weight: $weight-bold !default
+
+$card-content-background-color: transparent !default
+$card-content-padding: 1.5rem !default
+
+$card-footer-background-color: transparent !default
+$card-footer-border-top: 1px solid $border-light !default
+$card-footer-padding: 0.75rem !default
+
+$card-media-margin: $block-spacing !default
+
+.card
+ background-color: $card-background-color
+ box-shadow: $card-shadow
+ color: $card-color
+ max-width: 100%
+ position: relative
+
+.card-header
+ background-color: $card-header-background-color
+ align-items: stretch
+ box-shadow: $card-header-shadow
+ display: flex
+
+.card-header-title
+ align-items: center
+ color: $card-header-color
+ display: flex
+ flex-grow: 1
+ font-weight: $card-header-weight
+ padding: $card-header-padding
+ &.is-centered
+ justify-content: center
+
+.card-header-icon
+ align-items: center
+ cursor: pointer
+ display: flex
+ justify-content: center
+ padding: $card-header-padding
+
+.card-image
+ display: block
+ position: relative
+
+.card-content
+ background-color: $card-content-background-color
+ padding: $card-content-padding
+
+.card-footer
+ background-color: $card-footer-background-color
+ border-top: $card-footer-border-top
+ align-items: stretch
+ display: flex
+
+.card-footer-item
+ align-items: center
+ display: flex
+ flex-basis: 0
+ flex-grow: 1
+ flex-shrink: 0
+ justify-content: center
+ padding: $card-footer-padding
+ &:not(:last-child)
+ border-right: $card-footer-border-top
+
+// Combinations
+
+.card
+ .media:not(:last-child)
+ margin-bottom: $card-media-margin
diff --git a/docs/src/assets/bulma/components/dropdown.sass b/docs/src/assets/bulma/components/dropdown.sass
new file mode 100644
index 00000000..d62a6d88
--- /dev/null
+++ b/docs/src/assets/bulma/components/dropdown.sass
@@ -0,0 +1,81 @@
+$dropdown-menu-min-width: 12rem !default
+
+$dropdown-content-background-color: $scheme-main !default
+$dropdown-content-arrow: $link !default
+$dropdown-content-offset: 4px !default
+$dropdown-content-padding-bottom: 0.5rem !default
+$dropdown-content-padding-top: 0.5rem !default
+$dropdown-content-radius: $radius !default
+$dropdown-content-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default
+$dropdown-content-z: 20 !default
+
+$dropdown-item-color: $text !default
+$dropdown-item-hover-color: $scheme-invert !default
+$dropdown-item-hover-background-color: $background !default
+$dropdown-item-active-color: $link-invert !default
+$dropdown-item-active-background-color: $link !default
+
+$dropdown-divider-background-color: $border-light !default
+
+.dropdown
+ display: inline-flex
+ position: relative
+ vertical-align: top
+ &.is-active,
+ &.is-hoverable:hover
+ .dropdown-menu
+ display: block
+ &.is-right
+ .dropdown-menu
+ left: auto
+ right: 0
+ &.is-up
+ .dropdown-menu
+ bottom: 100%
+ padding-bottom: $dropdown-content-offset
+ padding-top: initial
+ top: auto
+
+.dropdown-menu
+ display: none
+ left: 0
+ min-width: $dropdown-menu-min-width
+ padding-top: $dropdown-content-offset
+ position: absolute
+ top: 100%
+ z-index: $dropdown-content-z
+
+.dropdown-content
+ background-color: $dropdown-content-background-color
+ border-radius: $dropdown-content-radius
+ box-shadow: $dropdown-content-shadow
+ padding-bottom: $dropdown-content-padding-bottom
+ padding-top: $dropdown-content-padding-top
+
+.dropdown-item
+ color: $dropdown-item-color
+ display: block
+ font-size: 0.875rem
+ line-height: 1.5
+ padding: 0.375rem 1rem
+ position: relative
+
+a.dropdown-item,
+button.dropdown-item
+ padding-right: 3rem
+ text-align: left
+ white-space: nowrap
+ width: 100%
+ &:hover
+ background-color: $dropdown-item-hover-background-color
+ color: $dropdown-item-hover-color
+ &.is-active
+ background-color: $dropdown-item-active-background-color
+ color: $dropdown-item-active-color
+
+.dropdown-divider
+ background-color: $dropdown-divider-background-color
+ border: none
+ display: block
+ height: 1px
+ margin: 0.5rem 0
diff --git a/docs/src/assets/bulma/components/level.sass b/docs/src/assets/bulma/components/level.sass
new file mode 100644
index 00000000..608f291e
--- /dev/null
+++ b/docs/src/assets/bulma/components/level.sass
@@ -0,0 +1,77 @@
+$level-item-spacing: ($block-spacing / 2) !default
+
+.level
+ @extend %block
+ align-items: center
+ justify-content: space-between
+ code
+ border-radius: $radius
+ img
+ display: inline-block
+ vertical-align: top
+ // Modifiers
+ &.is-mobile
+ display: flex
+ .level-left,
+ .level-right
+ display: flex
+ .level-left + .level-right
+ margin-top: 0
+ .level-item
+ &:not(:last-child)
+ margin-bottom: 0
+ margin-right: $level-item-spacing
+ &:not(.is-narrow)
+ flex-grow: 1
+ // Responsiveness
+ +tablet
+ display: flex
+ & > .level-item
+ &:not(.is-narrow)
+ flex-grow: 1
+
+.level-item
+ align-items: center
+ display: flex
+ flex-basis: auto
+ flex-grow: 0
+ flex-shrink: 0
+ justify-content: center
+ .title,
+ .subtitle
+ margin-bottom: 0
+ // Responsiveness
+ +mobile
+ &:not(:last-child)
+ margin-bottom: $level-item-spacing
+
+.level-left,
+.level-right
+ flex-basis: auto
+ flex-grow: 0
+ flex-shrink: 0
+ .level-item
+ // Modifiers
+ &.is-flexible
+ flex-grow: 1
+ // Responsiveness
+ +tablet
+ &:not(:last-child)
+ margin-right: $level-item-spacing
+
+.level-left
+ align-items: center
+ justify-content: flex-start
+ // Responsiveness
+ +mobile
+ & + .level-right
+ margin-top: 1.5rem
+ +tablet
+ display: flex
+
+.level-right
+ align-items: center
+ justify-content: flex-end
+ // Responsiveness
+ +tablet
+ display: flex
diff --git a/docs/src/assets/bulma/components/list.sass b/docs/src/assets/bulma/components/list.sass
new file mode 100644
index 00000000..bc99428a
--- /dev/null
+++ b/docs/src/assets/bulma/components/list.sass
@@ -0,0 +1,39 @@
+$list-background-color: $scheme-main !default
+$list-shadow: 0 2px 3px rgba($scheme-invert, 0.1), 0 0 0 1px rgba($scheme-invert, 0.1) !default
+$list-radius: $radius !default
+
+$list-item-border: 1px solid $border !default
+$list-item-color: $text !default
+$list-item-active-background-color: $link !default
+$list-item-active-color: $link-invert !default
+$list-item-hover-background-color: $background !default
+
+.list
+ @extend %block
+ background-color: $list-background-color
+ border-radius: $list-radius
+ box-shadow: $list-shadow
+ // &.is-hoverable > .list-item:hover:not(.is-active)
+ // background-color: $list-item-hover-background-color
+ // cursor: pointer
+
+.list-item
+ display: block
+ padding: 0.5em 1em
+ &:not(a)
+ color: $list-item-color
+ &:first-child
+ border-top-left-radius: $list-radius
+ border-top-right-radius: $list-radius
+ &:last-child
+ border-bottom-left-radius: $list-radius
+ border-bottom-right-radius: $list-radius
+ &:not(:last-child)
+ border-bottom: $list-item-border
+ &.is-active
+ background-color: $list-item-active-background-color
+ color: $list-item-active-color
+
+a.list-item
+ background-color: $list-item-hover-background-color
+ cursor: pointer
diff --git a/docs/src/assets/bulma/components/media.sass b/docs/src/assets/bulma/components/media.sass
new file mode 100644
index 00000000..a9ad114a
--- /dev/null
+++ b/docs/src/assets/bulma/components/media.sass
@@ -0,0 +1,50 @@
+$media-border-color: bulmaRgba($border, 0.5) !default
+
+.media
+ align-items: flex-start
+ display: flex
+ text-align: left
+ .content:not(:last-child)
+ margin-bottom: 0.75rem
+ .media
+ border-top: 1px solid $media-border-color
+ display: flex
+ padding-top: 0.75rem
+ .content:not(:last-child),
+ .control:not(:last-child)
+ margin-bottom: 0.5rem
+ .media
+ padding-top: 0.5rem
+ & + .media
+ margin-top: 0.5rem
+ & + .media
+ border-top: 1px solid $media-border-color
+ margin-top: 1rem
+ padding-top: 1rem
+ // Sizes
+ &.is-large
+ & + .media
+ margin-top: 1.5rem
+ padding-top: 1.5rem
+
+.media-left,
+.media-right
+ flex-basis: auto
+ flex-grow: 0
+ flex-shrink: 0
+
+.media-left
+ margin-right: 1rem
+
+.media-right
+ margin-left: 1rem
+
+.media-content
+ flex-basis: auto
+ flex-grow: 1
+ flex-shrink: 1
+ text-align: left
+
++mobile
+ .media-content
+ overflow-x: auto
diff --git a/docs/src/assets/bulma/components/menu.sass b/docs/src/assets/bulma/components/menu.sass
new file mode 100644
index 00000000..3de7e18d
--- /dev/null
+++ b/docs/src/assets/bulma/components/menu.sass
@@ -0,0 +1,57 @@
+$menu-item-color: $text !default
+$menu-item-radius: $radius-small !default
+$menu-item-hover-color: $text-strong !default
+$menu-item-hover-background-color: $background !default
+$menu-item-active-color: $link-invert !default
+$menu-item-active-background-color: $link !default
+
+$menu-list-border-left: 1px solid $border !default
+$menu-list-line-height: 1.25 !default
+$menu-list-link-padding: 0.5em 0.75em !default
+$menu-nested-list-margin: 0.75em !default
+$menu-nested-list-padding-left: 0.75em !default
+
+$menu-label-color: $text-light !default
+$menu-label-font-size: 0.75em !default
+$menu-label-letter-spacing: 0.1em !default
+$menu-label-spacing: 1em !default
+
+.menu
+ font-size: $size-normal
+ // Sizes
+ &.is-small
+ font-size: $size-small
+ &.is-medium
+ font-size: $size-medium
+ &.is-large
+ font-size: $size-large
+
+.menu-list
+ line-height: $menu-list-line-height
+ a
+ border-radius: $menu-item-radius
+ color: $menu-item-color
+ display: block
+ padding: $menu-list-link-padding
+ &:hover
+ background-color: $menu-item-hover-background-color
+ color: $menu-item-hover-color
+ // Modifiers
+ &.is-active
+ background-color: $menu-item-active-background-color
+ color: $menu-item-active-color
+ li
+ ul
+ border-left: $menu-list-border-left
+ margin: $menu-nested-list-margin
+ padding-left: $menu-nested-list-padding-left
+
+.menu-label
+ color: $menu-label-color
+ font-size: $menu-label-font-size
+ letter-spacing: $menu-label-letter-spacing
+ text-transform: uppercase
+ &:not(:first-child)
+ margin-top: $menu-label-spacing
+ &:not(:last-child)
+ margin-bottom: $menu-label-spacing
diff --git a/docs/src/assets/bulma/components/message.sass b/docs/src/assets/bulma/components/message.sass
new file mode 100644
index 00000000..89e4cc9a
--- /dev/null
+++ b/docs/src/assets/bulma/components/message.sass
@@ -0,0 +1,99 @@
+$message-background-color: $background !default
+$message-radius: $radius !default
+
+$message-header-background-color: $text !default
+$message-header-color: $text-invert !default
+$message-header-weight: $weight-bold !default
+$message-header-padding: 0.75em 1em !default
+$message-header-radius: $radius !default
+
+$message-body-border-color: $border !default
+$message-body-border-width: 0 0 0 4px !default
+$message-body-color: $text !default
+$message-body-padding: 1.25em 1.5em !default
+$message-body-radius: $radius !default
+
+$message-body-pre-background-color: $scheme-main !default
+$message-body-pre-code-background-color: transparent !default
+
+$message-header-body-border-width: 0 !default
+$message-colors: $colors !default
+
+.message
+ @extend %block
+ background-color: $message-background-color
+ border-radius: $message-radius
+ font-size: $size-normal
+ strong
+ color: currentColor
+ a:not(.button):not(.tag):not(.dropdown-item)
+ color: currentColor
+ text-decoration: underline
+ // Sizes
+ &.is-small
+ font-size: $size-small
+ &.is-medium
+ font-size: $size-medium
+ &.is-large
+ font-size: $size-large
+ // Colors
+ @each $name, $components in $message-colors
+ $color: nth($components, 1)
+ $color-invert: nth($components, 2)
+ $color-light: null
+ $color-dark: null
+
+ @if length($components) >= 3
+ $color-light: nth($components, 3)
+ @if length($components) >= 4
+ $color-dark: nth($components, 4)
+ @else
+ $color-luminance: colorLuminance($color)
+ $darken-percentage: $color-luminance * 70%
+ $desaturate-percentage: $color-luminance * 30%
+ $color-dark: desaturate(darken($color, $darken-percentage), $desaturate-percentage)
+ @else
+ $color-lightning: max((100% - lightness($color)) - 2%, 0%)
+ $color-light: lighten($color, $color-lightning)
+
+ &.is-#{$name}
+ background-color: $color-light
+ .message-header
+ background-color: $color
+ color: $color-invert
+ .message-body
+ border-color: $color
+ color: $color-dark
+
+.message-header
+ align-items: center
+ background-color: $message-header-background-color
+ border-radius: $message-header-radius $message-header-radius 0 0
+ color: $message-header-color
+ display: flex
+ font-weight: $message-header-weight
+ justify-content: space-between
+ line-height: 1.25
+ padding: $message-header-padding
+ position: relative
+ .delete
+ flex-grow: 0
+ flex-shrink: 0
+ margin-left: 0.75em
+ & + .message-body
+ border-width: $message-header-body-border-width
+ border-top-left-radius: 0
+ border-top-right-radius: 0
+
+.message-body
+ border-color: $message-body-border-color
+ border-radius: $message-body-radius
+ border-style: solid
+ border-width: $message-body-border-width
+ color: $message-body-color
+ padding: $message-body-padding
+ code,
+ pre
+ background-color: $message-body-pre-background-color
+ pre code
+ background-color: $message-body-pre-code-background-color
diff --git a/docs/src/assets/bulma/components/modal.sass b/docs/src/assets/bulma/components/modal.sass
new file mode 100644
index 00000000..377dfa78
--- /dev/null
+++ b/docs/src/assets/bulma/components/modal.sass
@@ -0,0 +1,113 @@
+$modal-z: 40 !default
+
+$modal-background-background-color: bulmaRgba($scheme-invert, 0.86) !default
+
+$modal-content-width: 640px !default
+$modal-content-margin-mobile: 20px !default
+$modal-content-spacing-mobile: 160px !default
+$modal-content-spacing-tablet: 40px !default
+
+$modal-close-dimensions: 40px !default
+$modal-close-right: 20px !default
+$modal-close-top: 20px !default
+
+$modal-card-spacing: 40px !default
+
+$modal-card-head-background-color: $background !default
+$modal-card-head-border-bottom: 1px solid $border !default
+$modal-card-head-padding: 20px !default
+$modal-card-head-radius: $radius-large !default
+
+$modal-card-title-color: $text-strong !default
+$modal-card-title-line-height: 1 !default
+$modal-card-title-size: $size-4 !default
+
+$modal-card-foot-radius: $radius-large !default
+$modal-card-foot-border-top: 1px solid $border !default
+
+$modal-card-body-background-color: $scheme-main !default
+$modal-card-body-padding: 20px !default
+
+.modal
+ @extend %overlay
+ align-items: center
+ display: none
+ flex-direction: column
+ justify-content: center
+ overflow: hidden
+ position: fixed
+ z-index: $modal-z
+ // Modifiers
+ &.is-active
+ display: flex
+
+.modal-background
+ @extend %overlay
+ background-color: $modal-background-background-color
+
+.modal-content,
+.modal-card
+ margin: 0 $modal-content-margin-mobile
+ max-height: calc(100vh - #{$modal-content-spacing-mobile})
+ overflow: auto
+ position: relative
+ width: 100%
+ // Responsiveness
+ +tablet
+ margin: 0 auto
+ max-height: calc(100vh - #{$modal-content-spacing-tablet})
+ width: $modal-content-width
+
+.modal-close
+ @extend %delete
+ background: none
+ height: $modal-close-dimensions
+ position: fixed
+ right: $modal-close-right
+ top: $modal-close-top
+ width: $modal-close-dimensions
+
+.modal-card
+ display: flex
+ flex-direction: column
+ max-height: calc(100vh - #{$modal-card-spacing})
+ overflow: hidden
+ -ms-overflow-y: visible
+
+.modal-card-head,
+.modal-card-foot
+ align-items: center
+ background-color: $modal-card-head-background-color
+ display: flex
+ flex-shrink: 0
+ justify-content: flex-start
+ padding: $modal-card-head-padding
+ position: relative
+
+.modal-card-head
+ border-bottom: $modal-card-head-border-bottom
+ border-top-left-radius: $modal-card-head-radius
+ border-top-right-radius: $modal-card-head-radius
+
+.modal-card-title
+ color: $modal-card-title-color
+ flex-grow: 1
+ flex-shrink: 0
+ font-size: $modal-card-title-size
+ line-height: $modal-card-title-line-height
+
+.modal-card-foot
+ border-bottom-left-radius: $modal-card-foot-radius
+ border-bottom-right-radius: $modal-card-foot-radius
+ border-top: $modal-card-foot-border-top
+ .button
+ &:not(:last-child)
+ margin-right: 0.5em
+
+.modal-card-body
+ +overflow-touch
+ background-color: $modal-card-body-background-color
+ flex-grow: 1
+ flex-shrink: 1
+ overflow: auto
+ padding: $modal-card-body-padding
diff --git a/docs/src/assets/bulma/components/navbar.sass b/docs/src/assets/bulma/components/navbar.sass
new file mode 100644
index 00000000..664558f7
--- /dev/null
+++ b/docs/src/assets/bulma/components/navbar.sass
@@ -0,0 +1,443 @@
+$navbar-background-color: $scheme-main !default
+$navbar-box-shadow-size: 0 2px 0 0 !default
+$navbar-box-shadow-color: $background !default
+$navbar-height: 3.25rem !default
+$navbar-padding-vertical: 1rem !default
+$navbar-padding-horizontal: 2rem !default
+$navbar-z: 30 !default
+$navbar-fixed-z: 30 !default
+
+$navbar-item-color: $text !default
+$navbar-item-hover-color: $link !default
+$navbar-item-hover-background-color: $scheme-main-bis !default
+$navbar-item-active-color: $scheme-invert !default
+$navbar-item-active-background-color: transparent !default
+$navbar-item-img-max-height: 1.75rem !default
+
+$navbar-burger-color: $navbar-item-color !default
+
+$navbar-tab-hover-background-color: transparent !default
+$navbar-tab-hover-border-bottom-color: $link !default
+$navbar-tab-active-color: $link !default
+$navbar-tab-active-background-color: transparent !default
+$navbar-tab-active-border-bottom-color: $link !default
+$navbar-tab-active-border-bottom-style: solid !default
+$navbar-tab-active-border-bottom-width: 3px !default
+
+$navbar-dropdown-background-color: $scheme-main !default
+$navbar-dropdown-border-top: 2px solid $border !default
+$navbar-dropdown-offset: -4px !default
+$navbar-dropdown-arrow: $link !default
+$navbar-dropdown-radius: $radius-large !default
+$navbar-dropdown-z: 20 !default
+
+$navbar-dropdown-boxed-radius: $radius-large !default
+$navbar-dropdown-boxed-shadow: 0 8px 8px rgba($scheme-invert, 0.1), 0 0 0 1px rgba($scheme-invert, 0.1) !default
+
+$navbar-dropdown-item-hover-color: $scheme-invert !default
+$navbar-dropdown-item-hover-background-color: $background !default
+$navbar-dropdown-item-active-color: $link !default
+$navbar-dropdown-item-active-background-color: $background !default
+
+$navbar-divider-background-color: $background !default
+$navbar-divider-height: 2px !default
+
+$navbar-bottom-box-shadow-size: 0 -2px 0 0 !default
+
+$navbar-breakpoint: $desktop !default
+
+=navbar-fixed
+ left: 0
+ position: fixed
+ right: 0
+ z-index: $navbar-fixed-z
+
+.navbar
+ background-color: $navbar-background-color
+ min-height: $navbar-height
+ position: relative
+ z-index: $navbar-z
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ $color-invert: nth($pair, 2)
+ &.is-#{$name}
+ background-color: $color
+ color: $color-invert
+ .navbar-brand
+ & > .navbar-item,
+ .navbar-link
+ color: $color-invert
+ & > a.navbar-item,
+ .navbar-link
+ &:focus,
+ &:hover,
+ &.is-active
+ background-color: darken($color, 5%)
+ color: $color-invert
+ .navbar-link
+ &::after
+ border-color: $color-invert
+ .navbar-burger
+ color: $color-invert
+ +from($navbar-breakpoint)
+ .navbar-start,
+ .navbar-end
+ & > .navbar-item,
+ .navbar-link
+ color: $color-invert
+ & > a.navbar-item,
+ .navbar-link
+ &:focus,
+ &:hover,
+ &.is-active
+ background-color: darken($color, 5%)
+ color: $color-invert
+ .navbar-link
+ &::after
+ border-color: $color-invert
+ .navbar-item.has-dropdown:focus .navbar-link,
+ .navbar-item.has-dropdown:hover .navbar-link,
+ .navbar-item.has-dropdown.is-active .navbar-link
+ background-color: darken($color, 5%)
+ color: $color-invert
+ .navbar-dropdown
+ a.navbar-item
+ &.is-active
+ background-color: $color
+ color: $color-invert
+ & > .container
+ align-items: stretch
+ display: flex
+ min-height: $navbar-height
+ width: 100%
+ &.has-shadow
+ box-shadow: $navbar-box-shadow-size $navbar-box-shadow-color
+ &.is-fixed-bottom,
+ &.is-fixed-top
+ +navbar-fixed
+ &.is-fixed-bottom
+ bottom: 0
+ &.has-shadow
+ box-shadow: $navbar-bottom-box-shadow-size $navbar-box-shadow-color
+ &.is-fixed-top
+ top: 0
+
+html,
+body
+ &.has-navbar-fixed-top
+ padding-top: $navbar-height
+ &.has-navbar-fixed-bottom
+ padding-bottom: $navbar-height
+
+.navbar-brand,
+.navbar-tabs
+ align-items: stretch
+ display: flex
+ flex-shrink: 0
+ min-height: $navbar-height
+
+.navbar-brand
+ a.navbar-item
+ &:focus,
+ &:hover
+ background-color: transparent
+
+.navbar-tabs
+ +overflow-touch
+ max-width: 100vw
+ overflow-x: auto
+ overflow-y: hidden
+
+.navbar-burger
+ color: $navbar-burger-color
+ +hamburger($navbar-height)
+ margin-left: auto
+
+.navbar-menu
+ display: none
+
+.navbar-item,
+.navbar-link
+ color: $navbar-item-color
+ display: block
+ line-height: 1.5
+ padding: 0.5rem 0.75rem
+ position: relative
+ .icon
+ &:only-child
+ margin-left: -0.25rem
+ margin-right: -0.25rem
+
+a.navbar-item,
+.navbar-link
+ cursor: pointer
+ &:focus,
+ &:focus-within,
+ &:hover,
+ &.is-active
+ background-color: $navbar-item-hover-background-color
+ color: $navbar-item-hover-color
+
+.navbar-item
+ display: block
+ flex-grow: 0
+ flex-shrink: 0
+ img
+ max-height: $navbar-item-img-max-height
+ &.has-dropdown
+ padding: 0
+ &.is-expanded
+ flex-grow: 1
+ flex-shrink: 1
+ &.is-tab
+ border-bottom: 1px solid transparent
+ min-height: $navbar-height
+ padding-bottom: calc(0.5rem - 1px)
+ &:focus,
+ &:hover
+ background-color: $navbar-tab-hover-background-color
+ border-bottom-color: $navbar-tab-hover-border-bottom-color
+ &.is-active
+ background-color: $navbar-tab-active-background-color
+ border-bottom-color: $navbar-tab-active-border-bottom-color
+ border-bottom-style: $navbar-tab-active-border-bottom-style
+ border-bottom-width: $navbar-tab-active-border-bottom-width
+ color: $navbar-tab-active-color
+ padding-bottom: calc(0.5rem - #{$navbar-tab-active-border-bottom-width})
+
+.navbar-content
+ flex-grow: 1
+ flex-shrink: 1
+
+.navbar-link:not(.is-arrowless)
+ padding-right: 2.5em
+ &::after
+ @extend %arrow
+ border-color: $navbar-dropdown-arrow
+ margin-top: -0.375em
+ right: 1.125em
+
+.navbar-dropdown
+ font-size: 0.875rem
+ padding-bottom: 0.5rem
+ padding-top: 0.5rem
+ .navbar-item
+ padding-left: 1.5rem
+ padding-right: 1.5rem
+
+.navbar-divider
+ background-color: $navbar-divider-background-color
+ border: none
+ display: none
+ height: $navbar-divider-height
+ margin: 0.5rem 0
+
++until($navbar-breakpoint)
+ .navbar > .container
+ display: block
+ .navbar-brand,
+ .navbar-tabs
+ .navbar-item
+ align-items: center
+ display: flex
+ .navbar-link
+ &::after
+ display: none
+ .navbar-menu
+ background-color: $navbar-background-color
+ box-shadow: 0 8px 16px rgba($scheme-invert, 0.1)
+ padding: 0.5rem 0
+ &.is-active
+ display: block
+ // Fixed navbar
+ .navbar
+ &.is-fixed-bottom-touch,
+ &.is-fixed-top-touch
+ +navbar-fixed
+ &.is-fixed-bottom-touch
+ bottom: 0
+ &.has-shadow
+ box-shadow: 0 -2px 3px rgba($scheme-invert, 0.1)
+ &.is-fixed-top-touch
+ top: 0
+ &.is-fixed-top,
+ &.is-fixed-top-touch
+ .navbar-menu
+ +overflow-touch
+ max-height: calc(100vh - #{$navbar-height})
+ overflow: auto
+ html,
+ body
+ &.has-navbar-fixed-top-touch
+ padding-top: $navbar-height
+ &.has-navbar-fixed-bottom-touch
+ padding-bottom: $navbar-height
+
++from($navbar-breakpoint)
+ .navbar,
+ .navbar-menu,
+ .navbar-start,
+ .navbar-end
+ align-items: stretch
+ display: flex
+ .navbar
+ min-height: $navbar-height
+ &.is-spaced
+ padding: $navbar-padding-vertical $navbar-padding-horizontal
+ .navbar-start,
+ .navbar-end
+ align-items: center
+ a.navbar-item,
+ .navbar-link
+ border-radius: $radius
+ &.is-transparent
+ a.navbar-item,
+ .navbar-link
+ &:focus,
+ &:hover,
+ &.is-active
+ background-color: transparent !important
+ .navbar-item.has-dropdown
+ &.is-active,
+ &.is-hoverable:focus,
+ &.is-hoverable:focus-within,
+ &.is-hoverable:hover
+ .navbar-link
+ background-color: transparent !important
+ .navbar-dropdown
+ a.navbar-item
+ &:focus,
+ &:hover
+ background-color: $navbar-dropdown-item-hover-background-color
+ color: $navbar-dropdown-item-hover-color
+ &.is-active
+ background-color: $navbar-dropdown-item-active-background-color
+ color: $navbar-dropdown-item-active-color
+ .navbar-burger
+ display: none
+ .navbar-item,
+ .navbar-link
+ align-items: center
+ display: flex
+ .navbar-item
+ display: flex
+ &.has-dropdown
+ align-items: stretch
+ &.has-dropdown-up
+ .navbar-link::after
+ transform: rotate(135deg) translate(0.25em, -0.25em)
+ .navbar-dropdown
+ border-bottom: $navbar-dropdown-border-top
+ border-radius: $navbar-dropdown-radius $navbar-dropdown-radius 0 0
+ border-top: none
+ bottom: 100%
+ box-shadow: 0 -8px 8px rgba($scheme-invert, 0.1)
+ top: auto
+ &.is-active,
+ &.is-hoverable:focus,
+ &.is-hoverable:focus-within,
+ &.is-hoverable:hover
+ .navbar-dropdown
+ display: block
+ .navbar.is-spaced &,
+ &.is-boxed
+ opacity: 1
+ pointer-events: auto
+ transform: translateY(0)
+ .navbar-menu
+ flex-grow: 1
+ flex-shrink: 0
+ .navbar-start
+ justify-content: flex-start
+ margin-right: auto
+ .navbar-end
+ justify-content: flex-end
+ margin-left: auto
+ .navbar-dropdown
+ background-color: $navbar-dropdown-background-color
+ border-bottom-left-radius: $navbar-dropdown-radius
+ border-bottom-right-radius: $navbar-dropdown-radius
+ border-top: $navbar-dropdown-border-top
+ box-shadow: 0 8px 8px rgba($scheme-invert, 0.1)
+ display: none
+ font-size: 0.875rem
+ left: 0
+ min-width: 100%
+ position: absolute
+ top: 100%
+ z-index: $navbar-dropdown-z
+ .navbar-item
+ padding: 0.375rem 1rem
+ white-space: nowrap
+ a.navbar-item
+ padding-right: 3rem
+ &:focus,
+ &:hover
+ background-color: $navbar-dropdown-item-hover-background-color
+ color: $navbar-dropdown-item-hover-color
+ &.is-active
+ background-color: $navbar-dropdown-item-active-background-color
+ color: $navbar-dropdown-item-active-color
+ .navbar.is-spaced &,
+ &.is-boxed
+ border-radius: $navbar-dropdown-boxed-radius
+ border-top: none
+ box-shadow: $navbar-dropdown-boxed-shadow
+ display: block
+ opacity: 0
+ pointer-events: none
+ top: calc(100% + (#{$navbar-dropdown-offset}))
+ transform: translateY(-5px)
+ transition-duration: $speed
+ transition-property: opacity, transform
+ &.is-right
+ left: auto
+ right: 0
+ .navbar-divider
+ display: block
+ .navbar > .container,
+ .container > .navbar
+ .navbar-brand
+ margin-left: -.75rem
+ .navbar-menu
+ margin-right: -.75rem
+ // Fixed navbar
+ .navbar
+ &.is-fixed-bottom-desktop,
+ &.is-fixed-top-desktop
+ +navbar-fixed
+ &.is-fixed-bottom-desktop
+ bottom: 0
+ &.has-shadow
+ box-shadow: 0 -2px 3px rgba($scheme-invert, 0.1)
+ &.is-fixed-top-desktop
+ top: 0
+ html,
+ body
+ &.has-navbar-fixed-top-desktop
+ padding-top: $navbar-height
+ &.has-navbar-fixed-bottom-desktop
+ padding-bottom: $navbar-height
+ &.has-spaced-navbar-fixed-top
+ padding-top: $navbar-height + ($navbar-padding-vertical * 2)
+ &.has-spaced-navbar-fixed-bottom
+ padding-bottom: $navbar-height + ($navbar-padding-vertical * 2)
+ // Hover/Active states
+ a.navbar-item,
+ .navbar-link
+ &.is-active
+ color: $navbar-item-active-color
+ &.is-active:not(:focus):not(:hover)
+ background-color: $navbar-item-active-background-color
+ .navbar-item.has-dropdown
+ &:focus,
+ &:hover,
+ &.is-active
+ .navbar-link
+ background-color: $navbar-item-hover-background-color
+
+// Combination
+
+.hero
+ &.is-fullheight-with-navbar
+ min-height: calc(100vh - #{$navbar-height})
diff --git a/docs/src/assets/bulma/components/pagination.sass b/docs/src/assets/bulma/components/pagination.sass
new file mode 100644
index 00000000..822c2e81
--- /dev/null
+++ b/docs/src/assets/bulma/components/pagination.sass
@@ -0,0 +1,150 @@
+$pagination-color: $text-strong !default
+$pagination-border-color: $border !default
+$pagination-margin: -0.25rem !default
+$pagination-min-width: $control-height !default
+
+$pagination-item-font-size: 1em !default
+$pagination-item-margin: 0.25rem !default
+$pagination-item-padding-left: 0.5em !default
+$pagination-item-padding-right: 0.5em !default
+
+$pagination-hover-color: $link-hover !default
+$pagination-hover-border-color: $link-hover-border !default
+
+$pagination-focus-color: $link-focus !default
+$pagination-focus-border-color: $link-focus-border !default
+
+$pagination-active-color: $link-active !default
+$pagination-active-border-color: $link-active-border !default
+
+$pagination-disabled-color: $text-light !default
+$pagination-disabled-background-color: $border !default
+$pagination-disabled-border-color: $border !default
+
+$pagination-current-color: $link-invert !default
+$pagination-current-background-color: $link !default
+$pagination-current-border-color: $link !default
+
+$pagination-ellipsis-color: $grey-light !default
+
+$pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2)
+
+.pagination
+ @extend %block
+ font-size: $size-normal
+ margin: $pagination-margin
+ // Sizes
+ &.is-small
+ font-size: $size-small
+ &.is-medium
+ font-size: $size-medium
+ &.is-large
+ font-size: $size-large
+ &.is-rounded
+ .pagination-previous,
+ .pagination-next
+ padding-left: 1em
+ padding-right: 1em
+ border-radius: $radius-rounded
+ .pagination-link
+ border-radius: $radius-rounded
+
+.pagination,
+.pagination-list
+ align-items: center
+ display: flex
+ justify-content: center
+ text-align: center
+
+.pagination-previous,
+.pagination-next,
+.pagination-link,
+.pagination-ellipsis
+ @extend %control
+ @extend %unselectable
+ font-size: $pagination-item-font-size
+ justify-content: center
+ margin: $pagination-item-margin
+ padding-left: $pagination-item-padding-left
+ padding-right: $pagination-item-padding-right
+ text-align: center
+
+.pagination-previous,
+.pagination-next,
+.pagination-link
+ border-color: $pagination-border-color
+ color: $pagination-color
+ min-width: $pagination-min-width
+ &:hover
+ border-color: $pagination-hover-border-color
+ color: $pagination-hover-color
+ &:focus
+ border-color: $pagination-focus-border-color
+ &:active
+ box-shadow: $pagination-shadow-inset
+ &[disabled]
+ background-color: $pagination-disabled-background-color
+ border-color: $pagination-disabled-border-color
+ box-shadow: none
+ color: $pagination-disabled-color
+ opacity: 0.5
+
+.pagination-previous,
+.pagination-next
+ padding-left: 0.75em
+ padding-right: 0.75em
+ white-space: nowrap
+
+.pagination-link
+ &.is-current
+ background-color: $pagination-current-background-color
+ border-color: $pagination-current-border-color
+ color: $pagination-current-color
+
+.pagination-ellipsis
+ color: $pagination-ellipsis-color
+ pointer-events: none
+
+.pagination-list
+ flex-wrap: wrap
+
++mobile
+ .pagination
+ flex-wrap: wrap
+ .pagination-previous,
+ .pagination-next
+ flex-grow: 1
+ flex-shrink: 1
+ .pagination-list
+ li
+ flex-grow: 1
+ flex-shrink: 1
+
++tablet
+ .pagination-list
+ flex-grow: 1
+ flex-shrink: 1
+ justify-content: flex-start
+ order: 1
+ .pagination-previous
+ order: 2
+ .pagination-next
+ order: 3
+ .pagination
+ justify-content: space-between
+ &.is-centered
+ .pagination-previous
+ order: 1
+ .pagination-list
+ justify-content: center
+ order: 2
+ .pagination-next
+ order: 3
+ &.is-right
+ .pagination-previous
+ order: 1
+ .pagination-next
+ order: 2
+ .pagination-list
+ justify-content: flex-end
+ order: 3
diff --git a/docs/src/assets/bulma/components/panel.sass b/docs/src/assets/bulma/components/panel.sass
new file mode 100644
index 00000000..c7b14877
--- /dev/null
+++ b/docs/src/assets/bulma/components/panel.sass
@@ -0,0 +1,119 @@
+$panel-margin: $block-spacing !default
+$panel-item-border: 1px solid $border-light !default
+$panel-radius: $radius-large !default
+$panel-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default
+
+$panel-heading-background-color: $border-light !default
+$panel-heading-color: $text-strong !default
+$panel-heading-line-height: 1.25 !default
+$panel-heading-padding: 0.75em 1em !default
+$panel-heading-radius: $radius !default
+$panel-heading-size: 1.25em !default
+$panel-heading-weight: $weight-bold !default
+
+$panel-tabs-font-size: 0.875em !default
+$panel-tab-border-bottom: 1px solid $border !default
+$panel-tab-active-border-bottom-color: $link-active-border !default
+$panel-tab-active-color: $link-active !default
+
+$panel-list-item-color: $text !default
+$panel-list-item-hover-color: $link !default
+
+$panel-block-color: $text-strong !default
+$panel-block-hover-background-color: $background !default
+$panel-block-active-border-left-color: $link !default
+$panel-block-active-color: $link-active !default
+$panel-block-active-icon-color: $link !default
+
+$panel-icon-color: $text-light !default
+$panel-colors: $colors !default
+
+.panel
+ border-radius: $panel-radius
+ box-shadow: $panel-shadow
+ font-size: $size-normal
+ &:not(:last-child)
+ margin-bottom: $panel-margin
+ // Colors
+ @each $name, $components in $panel-colors
+ $color: nth($components, 1)
+ $color-invert: nth($components, 2)
+ &.is-#{$name}
+ .panel-heading
+ background-color: $color
+ color: $color-invert
+ .panel-tabs a.is-active
+ border-bottom-color: $color
+ .panel-block.is-active .panel-icon
+ color: $color
+
+.panel-tabs,
+.panel-block
+ &:not(:last-child)
+ border-bottom: $panel-item-border
+
+.panel-heading
+ background-color: $panel-heading-background-color
+ border-radius: $panel-radius $panel-radius 0 0
+ color: $panel-heading-color
+ font-size: $panel-heading-size
+ font-weight: $panel-heading-weight
+ line-height: $panel-heading-line-height
+ padding: $panel-heading-padding
+
+.panel-tabs
+ align-items: flex-end
+ display: flex
+ font-size: $panel-tabs-font-size
+ justify-content: center
+ a
+ border-bottom: $panel-tab-border-bottom
+ margin-bottom: -1px
+ padding: 0.5em
+ // Modifiers
+ &.is-active
+ border-bottom-color: $panel-tab-active-border-bottom-color
+ color: $panel-tab-active-color
+
+.panel-list
+ a
+ color: $panel-list-item-color
+ &:hover
+ color: $panel-list-item-hover-color
+
+.panel-block
+ align-items: center
+ color: $panel-block-color
+ display: flex
+ justify-content: flex-start
+ padding: 0.5em 0.75em
+ input[type="checkbox"]
+ margin-right: 0.75em
+ & > .control
+ flex-grow: 1
+ flex-shrink: 1
+ width: 100%
+ &.is-wrapped
+ flex-wrap: wrap
+ &.is-active
+ border-left-color: $panel-block-active-border-left-color
+ color: $panel-block-active-color
+ .panel-icon
+ color: $panel-block-active-icon-color
+ &:last-child
+ border-bottom-left-radius: $panel-radius
+ border-bottom-right-radius: $panel-radius
+
+a.panel-block,
+label.panel-block
+ cursor: pointer
+ &:hover
+ background-color: $panel-block-hover-background-color
+
+.panel-icon
+ +fa(14px, 1em)
+ color: $panel-icon-color
+ margin-right: 0.75em
+ .fa
+ font-size: inherit
+ line-height: inherit
diff --git a/docs/src/assets/bulma/components/tabs.sass b/docs/src/assets/bulma/components/tabs.sass
new file mode 100644
index 00000000..8c28c257
--- /dev/null
+++ b/docs/src/assets/bulma/components/tabs.sass
@@ -0,0 +1,151 @@
+$tabs-border-bottom-color: $border !default
+$tabs-border-bottom-style: solid !default
+$tabs-border-bottom-width: 1px !default
+$tabs-link-color: $text !default
+$tabs-link-hover-border-bottom-color: $text-strong !default
+$tabs-link-hover-color: $text-strong !default
+$tabs-link-active-border-bottom-color: $link !default
+$tabs-link-active-color: $link !default
+$tabs-link-padding: 0.5em 1em !default
+
+$tabs-boxed-link-radius: $radius !default
+$tabs-boxed-link-hover-background-color: $background !default
+$tabs-boxed-link-hover-border-bottom-color: $border !default
+
+$tabs-boxed-link-active-background-color: $scheme-main !default
+$tabs-boxed-link-active-border-color: $border !default
+$tabs-boxed-link-active-border-bottom-color: transparent !default
+
+$tabs-toggle-link-border-color: $border !default
+$tabs-toggle-link-border-style: solid !default
+$tabs-toggle-link-border-width: 1px !default
+$tabs-toggle-link-hover-background-color: $background !default
+$tabs-toggle-link-hover-border-color: $border-hover !default
+$tabs-toggle-link-radius: $radius !default
+$tabs-toggle-link-active-background-color: $link !default
+$tabs-toggle-link-active-border-color: $link !default
+$tabs-toggle-link-active-color: $link-invert !default
+
+.tabs
+ @extend %block
+ +overflow-touch
+ @extend %unselectable
+ align-items: stretch
+ display: flex
+ font-size: $size-normal
+ justify-content: space-between
+ overflow: hidden
+ overflow-x: auto
+ white-space: nowrap
+ a
+ align-items: center
+ border-bottom-color: $tabs-border-bottom-color
+ border-bottom-style: $tabs-border-bottom-style
+ border-bottom-width: $tabs-border-bottom-width
+ color: $tabs-link-color
+ display: flex
+ justify-content: center
+ margin-bottom: -#{$tabs-border-bottom-width}
+ padding: $tabs-link-padding
+ vertical-align: top
+ &:hover
+ border-bottom-color: $tabs-link-hover-border-bottom-color
+ color: $tabs-link-hover-color
+ li
+ display: block
+ &.is-active
+ a
+ border-bottom-color: $tabs-link-active-border-bottom-color
+ color: $tabs-link-active-color
+ ul
+ align-items: center
+ border-bottom-color: $tabs-border-bottom-color
+ border-bottom-style: $tabs-border-bottom-style
+ border-bottom-width: $tabs-border-bottom-width
+ display: flex
+ flex-grow: 1
+ flex-shrink: 0
+ justify-content: flex-start
+ &.is-left
+ padding-right: 0.75em
+ &.is-center
+ flex: none
+ justify-content: center
+ padding-left: 0.75em
+ padding-right: 0.75em
+ &.is-right
+ justify-content: flex-end
+ padding-left: 0.75em
+ .icon
+ &:first-child
+ margin-right: 0.5em
+ &:last-child
+ margin-left: 0.5em
+ // Alignment
+ &.is-centered
+ ul
+ justify-content: center
+ &.is-right
+ ul
+ justify-content: flex-end
+ // Styles
+ &.is-boxed
+ a
+ border: 1px solid transparent
+ border-radius: $tabs-boxed-link-radius $tabs-boxed-link-radius 0 0
+ &:hover
+ background-color: $tabs-boxed-link-hover-background-color
+ border-bottom-color: $tabs-boxed-link-hover-border-bottom-color
+ li
+ &.is-active
+ a
+ background-color: $tabs-boxed-link-active-background-color
+ border-color: $tabs-boxed-link-active-border-color
+ border-bottom-color: $tabs-boxed-link-active-border-bottom-color !important
+ &.is-fullwidth
+ li
+ flex-grow: 1
+ flex-shrink: 0
+ &.is-toggle
+ a
+ border-color: $tabs-toggle-link-border-color
+ border-style: $tabs-toggle-link-border-style
+ border-width: $tabs-toggle-link-border-width
+ margin-bottom: 0
+ position: relative
+ &:hover
+ background-color: $tabs-toggle-link-hover-background-color
+ border-color: $tabs-toggle-link-hover-border-color
+ z-index: 2
+ li
+ & + li
+ margin-left: -#{$tabs-toggle-link-border-width}
+ &:first-child a
+ border-radius: $tabs-toggle-link-radius 0 0 $tabs-toggle-link-radius
+ &:last-child a
+ border-radius: 0 $tabs-toggle-link-radius $tabs-toggle-link-radius 0
+ &.is-active
+ a
+ background-color: $tabs-toggle-link-active-background-color
+ border-color: $tabs-toggle-link-active-border-color
+ color: $tabs-toggle-link-active-color
+ z-index: 1
+ ul
+ border-bottom: none
+ &.is-toggle-rounded
+ li
+ &:first-child a
+ border-bottom-left-radius: $radius-rounded
+ border-top-left-radius: $radius-rounded
+ padding-left: 1.25em
+ &:last-child a
+ border-bottom-right-radius: $radius-rounded
+ border-top-right-radius: $radius-rounded
+ padding-right: 1.25em
+ // Sizes
+ &.is-small
+ font-size: $size-small
+ &.is-medium
+ font-size: $size-medium
+ &.is-large
+ font-size: $size-large
diff --git a/docs/src/assets/bulma/elements/_all.sass b/docs/src/assets/bulma/elements/_all.sass
new file mode 100644
index 00000000..7490c00d
--- /dev/null
+++ b/docs/src/assets/bulma/elements/_all.sass
@@ -0,0 +1,15 @@
+@charset "utf-8"
+
+@import "box.sass"
+@import "button.sass"
+@import "container.sass"
+@import "content.sass"
+@import "icon.sass"
+@import "image.sass"
+@import "notification.sass"
+@import "progress.sass"
+@import "table.sass"
+@import "tag.sass"
+@import "title.sass"
+
+@import "other.sass"
diff --git a/docs/src/assets/bulma/elements/box.sass b/docs/src/assets/bulma/elements/box.sass
new file mode 100644
index 00000000..2fd18d49
--- /dev/null
+++ b/docs/src/assets/bulma/elements/box.sass
@@ -0,0 +1,24 @@
+$box-color: $text !default
+$box-background-color: $scheme-main !default
+$box-radius: $radius-large !default
+$box-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default
+$box-padding: 1.25rem !default
+
+$box-link-hover-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0 0 1px $link !default
+$box-link-active-shadow: inset 0 1px 2px rgba($scheme-invert, 0.2), 0 0 0 1px $link !default
+
+.box
+ @extend %block
+ background-color: $box-background-color
+ border-radius: $box-radius
+ box-shadow: $box-shadow
+ color: $box-color
+ display: block
+ padding: $box-padding
+
+a.box
+ &:hover,
+ &:focus
+ box-shadow: $box-link-hover-shadow
+ &:active
+ box-shadow: $box-link-active-shadow
diff --git a/docs/src/assets/bulma/elements/button.sass b/docs/src/assets/bulma/elements/button.sass
new file mode 100644
index 00000000..df5417c8
--- /dev/null
+++ b/docs/src/assets/bulma/elements/button.sass
@@ -0,0 +1,323 @@
+$button-color: $text-strong !default
+$button-background-color: $scheme-main !default
+$button-family: false !default
+
+$button-border-color: $border !default
+$button-border-width: $control-border-width !default
+
+$button-padding-vertical: calc(0.5em - #{$button-border-width}) !default
+$button-padding-horizontal: 1em !default
+
+$button-hover-color: $link-hover !default
+$button-hover-border-color: $link-hover-border !default
+
+$button-focus-color: $link-focus !default
+$button-focus-border-color: $link-focus-border !default
+$button-focus-box-shadow-size: 0 0 0 0.125em !default
+$button-focus-box-shadow-color: bulmaRgba($link, 0.25) !default
+
+$button-active-color: $link-active !default
+$button-active-border-color: $link-active-border !default
+
+$button-text-color: $text !default
+$button-text-decoration: underline !default
+$button-text-hover-background-color: $background !default
+$button-text-hover-color: $text-strong !default
+
+$button-disabled-background-color: $scheme-main !default
+$button-disabled-border-color: $border !default
+$button-disabled-shadow: none !default
+$button-disabled-opacity: 0.5 !default
+
+$button-static-color: $text-light !default
+$button-static-background-color: $scheme-main-ter !default
+$button-static-border-color: $border !default
+
+// The button sizes use mixins so they can be used at different breakpoints
+=button-small
+ border-radius: $radius-small
+ font-size: $size-small
+=button-normal
+ font-size: $size-normal
+=button-medium
+ font-size: $size-medium
+=button-large
+ font-size: $size-large
+
+.button
+ @extend %control
+ @extend %unselectable
+ background-color: $button-background-color
+ border-color: $button-border-color
+ border-width: $button-border-width
+ color: $button-color
+ cursor: pointer
+ @if $button-family
+ font-family: $button-family
+ justify-content: center
+ padding-bottom: $button-padding-vertical
+ padding-left: $button-padding-horizontal
+ padding-right: $button-padding-horizontal
+ padding-top: $button-padding-vertical
+ text-align: center
+ white-space: nowrap
+ strong
+ color: inherit
+ .icon
+ &,
+ &.is-small,
+ &.is-medium,
+ &.is-large
+ height: 1.5em
+ width: 1.5em
+ &:first-child:not(:last-child)
+ margin-left: calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width})
+ margin-right: $button-padding-horizontal / 4
+ &:last-child:not(:first-child)
+ margin-left: $button-padding-horizontal / 4
+ margin-right: calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width})
+ &:first-child:last-child
+ margin-left: calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width})
+ margin-right: calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width})
+ // States
+ &:hover,
+ &.is-hovered
+ border-color: $button-hover-border-color
+ color: $button-hover-color
+ &:focus,
+ &.is-focused
+ border-color: $button-focus-border-color
+ color: $button-focus-color
+ &:not(:active)
+ box-shadow: $button-focus-box-shadow-size $button-focus-box-shadow-color
+ &:active,
+ &.is-active
+ border-color: $button-active-border-color
+ color: $button-active-color
+ // Colors
+ &.is-text
+ background-color: transparent
+ border-color: transparent
+ color: $button-text-color
+ text-decoration: $button-text-decoration
+ &:hover,
+ &.is-hovered,
+ &:focus,
+ &.is-focused
+ background-color: $button-text-hover-background-color
+ color: $button-text-hover-color
+ &:active,
+ &.is-active
+ background-color: darken($button-text-hover-background-color, 5%)
+ color: $button-text-hover-color
+ &[disabled],
+ fieldset[disabled] &
+ background-color: transparent
+ border-color: transparent
+ box-shadow: none
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ $color-invert: nth($pair, 2)
+ &.is-#{$name}
+ background-color: $color
+ border-color: transparent
+ color: $color-invert
+ &:hover,
+ &.is-hovered
+ background-color: darken($color, 2.5%)
+ border-color: transparent
+ color: $color-invert
+ &:focus,
+ &.is-focused
+ border-color: transparent
+ color: $color-invert
+ &:not(:active)
+ box-shadow: $button-focus-box-shadow-size rgba($color, 0.25)
+ &:active,
+ &.is-active
+ background-color: darken($color, 5%)
+ border-color: transparent
+ color: $color-invert
+ &[disabled],
+ fieldset[disabled] &
+ background-color: $color
+ border-color: transparent
+ box-shadow: none
+ &.is-inverted
+ background-color: $color-invert
+ color: $color
+ &:hover,
+ &.is-hovered
+ background-color: darken($color-invert, 5%)
+ &[disabled],
+ fieldset[disabled] &
+ background-color: $color-invert
+ border-color: transparent
+ box-shadow: none
+ color: $color
+ &.is-loading
+ &::after
+ border-color: transparent transparent $color-invert $color-invert !important
+ &.is-outlined
+ background-color: transparent
+ border-color: $color
+ color: $color
+ &:hover,
+ &.is-hovered,
+ &:focus,
+ &.is-focused
+ background-color: $color
+ border-color: $color
+ color: $color-invert
+ &.is-loading
+ &::after
+ border-color: transparent transparent $color $color !important
+ &:hover,
+ &.is-hovered,
+ &:focus,
+ &.is-focused
+ &::after
+ border-color: transparent transparent $color-invert $color-invert !important
+ &[disabled],
+ fieldset[disabled] &
+ background-color: transparent
+ border-color: $color
+ box-shadow: none
+ color: $color
+ &.is-inverted.is-outlined
+ background-color: transparent
+ border-color: $color-invert
+ color: $color-invert
+ &:hover,
+ &.is-hovered,
+ &:focus,
+ &.is-focused
+ background-color: $color-invert
+ color: $color
+ &.is-loading
+ &:hover,
+ &.is-hovered,
+ &:focus,
+ &.is-focused
+ &::after
+ border-color: transparent transparent $color $color !important
+ &[disabled],
+ fieldset[disabled] &
+ background-color: transparent
+ border-color: $color-invert
+ box-shadow: none
+ color: $color-invert
+ // If light and dark colors are provided
+ @if length($pair) >= 4
+ $color-light: nth($pair, 3)
+ $color-dark: nth($pair, 4)
+ &.is-light
+ background-color: $color-light
+ color: $color-dark
+ &:hover,
+ &.is-hovered
+ background-color: darken($color-light, 2.5%)
+ border-color: transparent
+ color: $color-dark
+ &:active,
+ &.is-active
+ background-color: darken($color-light, 5%)
+ border-color: transparent
+ color: $color-dark
+ // Sizes
+ &.is-small
+ +button-small
+ &.is-normal
+ +button-normal
+ &.is-medium
+ +button-medium
+ &.is-large
+ +button-large
+ // Modifiers
+ &[disabled],
+ fieldset[disabled] &
+ background-color: $button-disabled-background-color
+ border-color: $button-disabled-border-color
+ box-shadow: $button-disabled-shadow
+ opacity: $button-disabled-opacity
+ &.is-fullwidth
+ display: flex
+ width: 100%
+ &.is-loading
+ color: transparent !important
+ pointer-events: none
+ &::after
+ @extend %loader
+ +center(1em)
+ position: absolute !important
+ &.is-static
+ background-color: $button-static-background-color
+ border-color: $button-static-border-color
+ color: $button-static-color
+ box-shadow: none
+ pointer-events: none
+ &.is-rounded
+ border-radius: $radius-rounded
+ padding-left: calc(#{$button-padding-horizontal} + 0.25em)
+ padding-right: calc(#{$button-padding-horizontal} + 0.25em)
+
+.buttons
+ align-items: center
+ display: flex
+ flex-wrap: wrap
+ justify-content: flex-start
+ .button
+ margin-bottom: 0.5rem
+ &:not(:last-child):not(.is-fullwidth)
+ margin-right: 0.5rem
+ &:last-child
+ margin-bottom: -0.5rem
+ &:not(:last-child)
+ margin-bottom: 1rem
+ // Sizes
+ &.are-small
+ .button:not(.is-normal):not(.is-medium):not(.is-large)
+ +button-small
+ &.are-medium
+ .button:not(.is-small):not(.is-normal):not(.is-large)
+ +button-medium
+ &.are-large
+ .button:not(.is-small):not(.is-normal):not(.is-medium)
+ +button-large
+ &.has-addons
+ .button
+ &:not(:first-child)
+ border-bottom-left-radius: 0
+ border-top-left-radius: 0
+ &:not(:last-child)
+ border-bottom-right-radius: 0
+ border-top-right-radius: 0
+ margin-right: -1px
+ &:last-child
+ margin-right: 0
+ &:hover,
+ &.is-hovered
+ z-index: 2
+ &:focus,
+ &.is-focused,
+ &:active,
+ &.is-active,
+ &.is-selected
+ z-index: 3
+ &:hover
+ z-index: 4
+ &.is-expanded
+ flex-grow: 1
+ flex-shrink: 1
+ &.is-centered
+ justify-content: center
+ &:not(.has-addons)
+ .button:not(.is-fullwidth)
+ margin-left: 0.25rem
+ margin-right: 0.25rem
+ &.is-right
+ justify-content: flex-end
+ &:not(.has-addons)
+ .button:not(.is-fullwidth)
+ margin-left: 0.25rem
+ margin-right: 0.25rem
diff --git a/docs/src/assets/bulma/elements/container.sass b/docs/src/assets/bulma/elements/container.sass
new file mode 100644
index 00000000..d88eb94a
--- /dev/null
+++ b/docs/src/assets/bulma/elements/container.sass
@@ -0,0 +1,24 @@
+$container-offset: (2 * $gap) !default
+
+.container
+ flex-grow: 1
+ margin: 0 auto
+ position: relative
+ width: auto
+ &.is-fluid
+ max-width: none
+ padding-left: $gap
+ padding-right: $gap
+ width: 100%
+ +desktop
+ max-width: $desktop - $container-offset
+ +until-widescreen
+ &.is-widescreen
+ max-width: $widescreen - $container-offset
+ +until-fullhd
+ &.is-fullhd
+ max-width: $fullhd - $container-offset
+ +widescreen
+ max-width: $widescreen - $container-offset
+ +fullhd
+ max-width: $fullhd - $container-offset
diff --git a/docs/src/assets/bulma/elements/content.sass b/docs/src/assets/bulma/elements/content.sass
new file mode 100644
index 00000000..001419ab
--- /dev/null
+++ b/docs/src/assets/bulma/elements/content.sass
@@ -0,0 +1,155 @@
+$content-heading-color: $text-strong !default
+$content-heading-weight: $weight-semibold !default
+$content-heading-line-height: 1.125 !default
+
+$content-blockquote-background-color: $background !default
+$content-blockquote-border-left: 5px solid $border !default
+$content-blockquote-padding: 1.25em 1.5em !default
+
+$content-pre-padding: 1.25em 1.5em !default
+
+$content-table-cell-border: 1px solid $border !default
+$content-table-cell-border-width: 0 0 1px !default
+$content-table-cell-padding: 0.5em 0.75em !default
+$content-table-cell-heading-color: $text-strong !default
+$content-table-head-cell-border-width: 0 0 2px !default
+$content-table-head-cell-color: $text-strong !default
+$content-table-foot-cell-border-width: 2px 0 0 !default
+$content-table-foot-cell-color: $text-strong !default
+
+.content
+ @extend %block
+ // Inline
+ li + li
+ margin-top: 0.25em
+ // Block
+ p,
+ dl,
+ ol,
+ ul,
+ blockquote,
+ pre,
+ table
+ &:not(:last-child)
+ margin-bottom: 1em
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6
+ color: $content-heading-color
+ font-weight: $content-heading-weight
+ line-height: $content-heading-line-height
+ h1
+ font-size: 2em
+ margin-bottom: 0.5em
+ &:not(:first-child)
+ margin-top: 1em
+ h2
+ font-size: 1.75em
+ margin-bottom: 0.5714em
+ &:not(:first-child)
+ margin-top: 1.1428em
+ h3
+ font-size: 1.5em
+ margin-bottom: 0.6666em
+ &:not(:first-child)
+ margin-top: 1.3333em
+ h4
+ font-size: 1.25em
+ margin-bottom: 0.8em
+ h5
+ font-size: 1.125em
+ margin-bottom: 0.8888em
+ h6
+ font-size: 1em
+ margin-bottom: 1em
+ blockquote
+ background-color: $content-blockquote-background-color
+ border-left: $content-blockquote-border-left
+ padding: $content-blockquote-padding
+ ol
+ list-style-position: outside
+ margin-left: 2em
+ margin-top: 1em
+ &:not([type])
+ list-style-type: decimal
+ &.is-lower-alpha
+ list-style-type: lower-alpha
+ &.is-lower-roman
+ list-style-type: lower-roman
+ &.is-upper-alpha
+ list-style-type: upper-alpha
+ &.is-upper-roman
+ list-style-type: upper-roman
+ ul
+ list-style: disc outside
+ margin-left: 2em
+ margin-top: 1em
+ ul
+ list-style-type: circle
+ margin-top: 0.5em
+ ul
+ list-style-type: square
+ dd
+ margin-left: 2em
+ figure
+ margin-left: 2em
+ margin-right: 2em
+ text-align: center
+ &:not(:first-child)
+ margin-top: 2em
+ &:not(:last-child)
+ margin-bottom: 2em
+ img
+ display: inline-block
+ figcaption
+ font-style: italic
+ pre
+ +overflow-touch
+ overflow-x: auto
+ padding: $content-pre-padding
+ white-space: pre
+ word-wrap: normal
+ sup,
+ sub
+ font-size: 75%
+ table
+ width: 100%
+ td,
+ th
+ border: $content-table-cell-border
+ border-width: $content-table-cell-border-width
+ padding: $content-table-cell-padding
+ vertical-align: top
+ th
+ color: $content-table-cell-heading-color
+ &:not([align])
+ text-align: left
+ thead
+ td,
+ th
+ border-width: $content-table-head-cell-border-width
+ color: $content-table-head-cell-color
+ tfoot
+ td,
+ th
+ border-width: $content-table-foot-cell-border-width
+ color: $content-table-foot-cell-color
+ tbody
+ tr
+ &:last-child
+ td,
+ th
+ border-bottom-width: 0
+ .tabs
+ li + li
+ margin-top: 0
+ // Sizes
+ &.is-small
+ font-size: $size-small
+ &.is-medium
+ font-size: $size-medium
+ &.is-large
+ font-size: $size-large
diff --git a/docs/src/assets/bulma/elements/form.sass b/docs/src/assets/bulma/elements/form.sass
new file mode 100644
index 00000000..3122dc4c
--- /dev/null
+++ b/docs/src/assets/bulma/elements/form.sass
@@ -0,0 +1 @@
+@warn "The form.sass file is DEPRECATED. It has moved into its own /form folder. Please import sass/form/_all instead."
diff --git a/docs/src/assets/bulma/elements/icon.sass b/docs/src/assets/bulma/elements/icon.sass
new file mode 100644
index 00000000..988546c7
--- /dev/null
+++ b/docs/src/assets/bulma/elements/icon.sass
@@ -0,0 +1,21 @@
+$icon-dimensions: 1.5rem !default
+$icon-dimensions-small: 1rem !default
+$icon-dimensions-medium: 2rem !default
+$icon-dimensions-large: 3rem !default
+
+.icon
+ align-items: center
+ display: inline-flex
+ justify-content: center
+ height: $icon-dimensions
+ width: $icon-dimensions
+ // Sizes
+ &.is-small
+ height: $icon-dimensions-small
+ width: $icon-dimensions-small
+ &.is-medium
+ height: $icon-dimensions-medium
+ width: $icon-dimensions-medium
+ &.is-large
+ height: $icon-dimensions-large
+ width: $icon-dimensions-large
diff --git a/docs/src/assets/bulma/elements/image.sass b/docs/src/assets/bulma/elements/image.sass
new file mode 100644
index 00000000..7547abcf
--- /dev/null
+++ b/docs/src/assets/bulma/elements/image.sass
@@ -0,0 +1,71 @@
+$dimensions: 16 24 32 48 64 96 128 !default
+
+.image
+ display: block
+ position: relative
+ img
+ display: block
+ height: auto
+ width: 100%
+ &.is-rounded
+ border-radius: $radius-rounded
+ &.is-fullwidth
+ width: 100%
+ // Ratio
+ &.is-square,
+ &.is-1by1,
+ &.is-5by4,
+ &.is-4by3,
+ &.is-3by2,
+ &.is-5by3,
+ &.is-16by9,
+ &.is-2by1,
+ &.is-3by1,
+ &.is-4by5,
+ &.is-3by4,
+ &.is-2by3,
+ &.is-3by5,
+ &.is-9by16,
+ &.is-1by2,
+ &.is-1by3
+ img,
+ .has-ratio
+ @extend %overlay
+ height: 100%
+ width: 100%
+ &.is-square,
+ &.is-1by1
+ padding-top: 100%
+ &.is-5by4
+ padding-top: 80%
+ &.is-4by3
+ padding-top: 75%
+ &.is-3by2
+ padding-top: 66.6666%
+ &.is-5by3
+ padding-top: 60%
+ &.is-16by9
+ padding-top: 56.25%
+ &.is-2by1
+ padding-top: 50%
+ &.is-3by1
+ padding-top: 33.3333%
+ &.is-4by5
+ padding-top: 125%
+ &.is-3by4
+ padding-top: 133.3333%
+ &.is-2by3
+ padding-top: 150%
+ &.is-3by5
+ padding-top: 166.6666%
+ &.is-9by16
+ padding-top: 177.7777%
+ &.is-1by2
+ padding-top: 200%
+ &.is-1by3
+ padding-top: 300%
+ // Sizes
+ @each $dimension in $dimensions
+ &.is-#{$dimension}x#{$dimension}
+ height: $dimension * 1px
+ width: $dimension * 1px
diff --git a/docs/src/assets/bulma/elements/notification.sass b/docs/src/assets/bulma/elements/notification.sass
new file mode 100644
index 00000000..32a0ee13
--- /dev/null
+++ b/docs/src/assets/bulma/elements/notification.sass
@@ -0,0 +1,43 @@
+$notification-background-color: $background !default
+$notification-code-background-color: $scheme-main !default
+$notification-radius: $radius !default
+$notification-padding: 1.25rem 2.5rem 1.25rem 1.5rem !default
+
+.notification
+ @extend %block
+ background-color: $notification-background-color
+ border-radius: $notification-radius
+ padding: $notification-padding
+ position: relative
+ a:not(.button):not(.dropdown-item)
+ color: currentColor
+ text-decoration: underline
+ strong
+ color: currentColor
+ code,
+ pre
+ background: $notification-code-background-color
+ pre code
+ background: transparent
+ & > .delete
+ position: absolute
+ right: 0.5rem
+ top: 0.5rem
+ .title,
+ .subtitle,
+ .content
+ color: currentColor
+ // Colors
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ $color-invert: nth($pair, 2)
+ &.is-#{$name}
+ background-color: $color
+ color: $color-invert
+ // If light and dark colors are provided
+ @if length($pair) >= 4
+ $color-light: nth($pair, 3)
+ $color-dark: nth($pair, 4)
+ &.is-light
+ background-color: $color-light
+ color: $color-dark
diff --git a/docs/src/assets/bulma/elements/other.sass b/docs/src/assets/bulma/elements/other.sass
new file mode 100644
index 00000000..5725617c
--- /dev/null
+++ b/docs/src/assets/bulma/elements/other.sass
@@ -0,0 +1,39 @@
+.block
+ @extend %block
+
+.delete
+ @extend %delete
+
+.heading
+ display: block
+ font-size: 11px
+ letter-spacing: 1px
+ margin-bottom: 5px
+ text-transform: uppercase
+
+.highlight
+ @extend %block
+ font-weight: $weight-normal
+ max-width: 100%
+ overflow: hidden
+ padding: 0
+ pre
+ overflow: auto
+ max-width: 100%
+
+.loader
+ @extend %loader
+
+.number
+ align-items: center
+ background-color: $background
+ border-radius: $radius-rounded
+ display: inline-flex
+ font-size: $size-medium
+ height: 2em
+ justify-content: center
+ margin-right: 1.5rem
+ min-width: 2.5em
+ padding: 0.25rem 0.5rem
+ text-align: center
+ vertical-align: top
diff --git a/docs/src/assets/bulma/elements/progress.sass b/docs/src/assets/bulma/elements/progress.sass
new file mode 100644
index 00000000..bb43bb60
--- /dev/null
+++ b/docs/src/assets/bulma/elements/progress.sass
@@ -0,0 +1,67 @@
+$progress-bar-background-color: $border-light !default
+$progress-value-background-color: $text !default
+$progress-border-radius: $radius-rounded !default
+
+$progress-indeterminate-duration: 1.5s !default
+
+.progress
+ @extend %block
+ -moz-appearance: none
+ -webkit-appearance: none
+ border: none
+ border-radius: $progress-border-radius
+ display: block
+ height: $size-normal
+ overflow: hidden
+ padding: 0
+ width: 100%
+ &::-webkit-progress-bar
+ background-color: $progress-bar-background-color
+ &::-webkit-progress-value
+ background-color: $progress-value-background-color
+ &::-moz-progress-bar
+ background-color: $progress-value-background-color
+ &::-ms-fill
+ background-color: $progress-value-background-color
+ border: none
+ // Colors
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ &.is-#{$name}
+ &::-webkit-progress-value
+ background-color: $color
+ &::-moz-progress-bar
+ background-color: $color
+ &::-ms-fill
+ background-color: $color
+ &:indeterminate
+ background-image: linear-gradient(to right, $color 30%, $progress-bar-background-color 30%)
+
+ &:indeterminate
+ animation-duration: $progress-indeterminate-duration
+ animation-iteration-count: infinite
+ animation-name: moveIndeterminate
+ animation-timing-function: linear
+ background-color: $progress-bar-background-color
+ background-image: linear-gradient(to right, $text 30%, $progress-bar-background-color 30%)
+ background-position: top left
+ background-repeat: no-repeat
+ background-size: 150% 150%
+ &::-webkit-progress-bar
+ background-color: transparent
+ &::-moz-progress-bar
+ background-color: transparent
+
+ // Sizes
+ &.is-small
+ height: $size-small
+ &.is-medium
+ height: $size-medium
+ &.is-large
+ height: $size-large
+
+@keyframes moveIndeterminate
+ from
+ background-position: 200% 0
+ to
+ background-position: -200% 0
diff --git a/docs/src/assets/bulma/elements/table.sass b/docs/src/assets/bulma/elements/table.sass
new file mode 100644
index 00000000..2e6036ad
--- /dev/null
+++ b/docs/src/assets/bulma/elements/table.sass
@@ -0,0 +1,127 @@
+$table-color: $text-strong !default
+$table-background-color: $scheme-main !default
+
+$table-cell-border: 1px solid $border !default
+$table-cell-border-width: 0 0 1px !default
+$table-cell-padding: 0.5em 0.75em !default
+$table-cell-heading-color: $text-strong !default
+
+$table-head-cell-border-width: 0 0 2px !default
+$table-head-cell-color: $text-strong !default
+$table-foot-cell-border-width: 2px 0 0 !default
+$table-foot-cell-color: $text-strong !default
+
+$table-head-background-color: transparent !default
+$table-body-background-color: transparent !default
+$table-foot-background-color: transparent !default
+
+$table-row-hover-background-color: $scheme-main-bis !default
+
+$table-row-active-background-color: $primary !default
+$table-row-active-color: $primary-invert !default
+
+$table-striped-row-even-background-color: $scheme-main-bis !default
+$table-striped-row-even-hover-background-color: $scheme-main-ter !default
+
+.table
+ @extend %block
+ background-color: $table-background-color
+ color: $table-color
+ td,
+ th
+ border: $table-cell-border
+ border-width: $table-cell-border-width
+ padding: $table-cell-padding
+ vertical-align: top
+ // Colors
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ $color-invert: nth($pair, 2)
+ &.is-#{$name}
+ background-color: $color
+ border-color: $color
+ color: $color-invert
+ // Modifiers
+ &.is-narrow
+ white-space: nowrap
+ width: 1%
+ &.is-selected
+ background-color: $table-row-active-background-color
+ color: $table-row-active-color
+ a,
+ strong
+ color: currentColor
+ th
+ color: $table-cell-heading-color
+ &:not([align])
+ text-align: left
+ tr
+ &.is-selected
+ background-color: $table-row-active-background-color
+ color: $table-row-active-color
+ a,
+ strong
+ color: currentColor
+ td,
+ th
+ border-color: $table-row-active-color
+ color: currentColor
+ thead
+ background-color: $table-head-background-color
+ td,
+ th
+ border-width: $table-head-cell-border-width
+ color: $table-head-cell-color
+ tfoot
+ background-color: $table-foot-background-color
+ td,
+ th
+ border-width: $table-foot-cell-border-width
+ color: $table-foot-cell-color
+ tbody
+ background-color: $table-body-background-color
+ tr
+ &:last-child
+ td,
+ th
+ border-bottom-width: 0
+ // Modifiers
+ &.is-bordered
+ td,
+ th
+ border-width: 1px
+ tr
+ &:last-child
+ td,
+ th
+ border-bottom-width: 1px
+ &.is-fullwidth
+ width: 100%
+ &.is-hoverable
+ tbody
+ tr:not(.is-selected)
+ &:hover
+ background-color: $table-row-hover-background-color
+ &.is-striped
+ tbody
+ tr:not(.is-selected)
+ &:hover
+ background-color: $table-row-hover-background-color
+ &:nth-child(even)
+ background-color: $table-striped-row-even-hover-background-color
+ &.is-narrow
+ td,
+ th
+ padding: 0.25em 0.5em
+ &.is-striped
+ tbody
+ tr:not(.is-selected)
+ &:nth-child(even)
+ background-color: $table-striped-row-even-background-color
+
+.table-container
+ @extend %block
+ +overflow-touch
+ overflow: auto
+ overflow-y: hidden
+ max-width: 100%
diff --git a/docs/src/assets/bulma/elements/tag.sass b/docs/src/assets/bulma/elements/tag.sass
new file mode 100644
index 00000000..e0fb89ef
--- /dev/null
+++ b/docs/src/assets/bulma/elements/tag.sass
@@ -0,0 +1,128 @@
+$tag-background-color: $background !default
+$tag-color: $text !default
+$tag-radius: $radius !default
+$tag-delete-margin: 1px !default
+
+.tags
+ align-items: center
+ display: flex
+ flex-wrap: wrap
+ justify-content: flex-start
+ .tag
+ margin-bottom: 0.5rem
+ &:not(:last-child)
+ margin-right: 0.5rem
+ &:last-child
+ margin-bottom: -0.5rem
+ &:not(:last-child)
+ margin-bottom: 1rem
+ // Sizes
+ &.are-medium
+ .tag:not(.is-normal):not(.is-large)
+ font-size: $size-normal
+ &.are-large
+ .tag:not(.is-normal):not(.is-medium)
+ font-size: $size-medium
+ &.is-centered
+ justify-content: center
+ .tag
+ margin-right: 0.25rem
+ margin-left: 0.25rem
+ &.is-right
+ justify-content: flex-end
+ .tag
+ &:not(:first-child)
+ margin-left: 0.5rem
+ &:not(:last-child)
+ margin-right: 0
+ &.has-addons
+ .tag
+ margin-right: 0
+ &:not(:first-child)
+ margin-left: 0
+ border-bottom-left-radius: 0
+ border-top-left-radius: 0
+ &:not(:last-child)
+ border-bottom-right-radius: 0
+ border-top-right-radius: 0
+
+.tag:not(body)
+ align-items: center
+ background-color: $tag-background-color
+ border-radius: $tag-radius
+ color: $tag-color
+ display: inline-flex
+ font-size: $size-small
+ height: 2em
+ justify-content: center
+ line-height: 1.5
+ padding-left: 0.75em
+ padding-right: 0.75em
+ white-space: nowrap
+ .delete
+ margin-left: 0.25rem
+ margin-right: -0.375rem
+ // Colors
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ $color-invert: nth($pair, 2)
+ &.is-#{$name}
+ background-color: $color
+ color: $color-invert
+ // If a light and dark colors are provided
+ @if length($pair) > 3
+ $color-light: nth($pair, 3)
+ $color-dark: nth($pair, 4)
+ &.is-light
+ background-color: $color-light
+ color: $color-dark
+ // Sizes
+ &.is-normal
+ font-size: $size-small
+ &.is-medium
+ font-size: $size-normal
+ &.is-large
+ font-size: $size-medium
+ .icon
+ &:first-child:not(:last-child)
+ margin-left: -0.375em
+ margin-right: 0.1875em
+ &:last-child:not(:first-child)
+ margin-left: 0.1875em
+ margin-right: -0.375em
+ &:first-child:last-child
+ margin-left: -0.375em
+ margin-right: -0.375em
+ // Modifiers
+ &.is-delete
+ margin-left: $tag-delete-margin
+ padding: 0
+ position: relative
+ width: 2em
+ &::before,
+ &::after
+ background-color: currentColor
+ content: ""
+ display: block
+ left: 50%
+ position: absolute
+ top: 50%
+ transform: translateX(-50%) translateY(-50%) rotate(45deg)
+ transform-origin: center center
+ &::before
+ height: 1px
+ width: 50%
+ &::after
+ height: 50%
+ width: 1px
+ &:hover,
+ &:focus
+ background-color: darken($tag-background-color, 5%)
+ &:active
+ background-color: darken($tag-background-color, 10%)
+ &.is-rounded
+ border-radius: $radius-rounded
+
+a.tag
+ &:hover
+ text-decoration: underline
diff --git a/docs/src/assets/bulma/elements/title.sass b/docs/src/assets/bulma/elements/title.sass
new file mode 100644
index 00000000..fa9947dd
--- /dev/null
+++ b/docs/src/assets/bulma/elements/title.sass
@@ -0,0 +1,70 @@
+$title-color: $text-strong !default
+$title-family: false !default
+$title-size: $size-3 !default
+$title-weight: $weight-semibold !default
+$title-line-height: 1.125 !default
+$title-strong-color: inherit !default
+$title-strong-weight: inherit !default
+$title-sub-size: 0.75em !default
+$title-sup-size: 0.75em !default
+
+$subtitle-color: $text !default
+$subtitle-family: false !default
+$subtitle-size: $size-5 !default
+$subtitle-weight: $weight-normal !default
+$subtitle-line-height: 1.25 !default
+$subtitle-strong-color: $text-strong !default
+$subtitle-strong-weight: $weight-semibold !default
+$subtitle-negative-margin: -1.25rem !default
+
+.title,
+.subtitle
+ @extend %block
+ word-break: break-word
+ em,
+ span
+ font-weight: inherit
+ sub
+ font-size: $title-sub-size
+ sup
+ font-size: $title-sup-size
+ .tag
+ vertical-align: middle
+
+.title
+ color: $title-color
+ @if $title-family
+ font-family: $title-family
+ font-size: $title-size
+ font-weight: $title-weight
+ line-height: $title-line-height
+ strong
+ color: $title-strong-color
+ font-weight: $title-strong-weight
+ & + .highlight
+ margin-top: -0.75rem
+ &:not(.is-spaced) + .subtitle
+ margin-top: $subtitle-negative-margin
+ // Sizes
+ @each $size in $sizes
+ $i: index($sizes, $size)
+ &.is-#{$i}
+ font-size: $size
+
+.subtitle
+ color: $subtitle-color
+ @if $subtitle-family
+ font-family: $subtitle-family
+ font-size: $subtitle-size
+ font-weight: $subtitle-weight
+ line-height: $subtitle-line-height
+ strong
+ color: $subtitle-strong-color
+ font-weight: $subtitle-strong-weight
+ &:not(.is-spaced) + .title
+ margin-top: $subtitle-negative-margin
+ // Sizes
+ @each $size in $sizes
+ $i: index($sizes, $size)
+ &.is-#{$i}
+ font-size: $size
diff --git a/docs/src/assets/bulma/form/_all.sass b/docs/src/assets/bulma/form/_all.sass
new file mode 100644
index 00000000..d9a2b955
--- /dev/null
+++ b/docs/src/assets/bulma/form/_all.sass
@@ -0,0 +1,8 @@
+@charset "utf-8"
+
+@import "shared.sass"
+@import "input-textarea.sass"
+@import "checkbox-radio.sass"
+@import "select.sass"
+@import "file.sass"
+@import "tools.sass"
diff --git a/docs/src/assets/bulma/form/checkbox-radio.sass b/docs/src/assets/bulma/form/checkbox-radio.sass
new file mode 100644
index 00000000..d9f3ff0c
--- /dev/null
+++ b/docs/src/assets/bulma/form/checkbox-radio.sass
@@ -0,0 +1,21 @@
+%checkbox-radio
+ cursor: pointer
+ display: inline-block
+ line-height: 1.25
+ position: relative
+ input
+ cursor: pointer
+ &:hover
+ color: $input-hover-color
+ &[disabled],
+ fieldset[disabled] &
+ color: $input-disabled-color
+ cursor: not-allowed
+
+.checkbox
+ @extend %checkbox-radio
+
+.radio
+ @extend %checkbox-radio
+ & + .radio
+ margin-left: 0.5em
diff --git a/docs/src/assets/bulma/form/file.sass b/docs/src/assets/bulma/form/file.sass
new file mode 100644
index 00000000..41cc0215
--- /dev/null
+++ b/docs/src/assets/bulma/form/file.sass
@@ -0,0 +1,180 @@
+$file-border-color: $border !default
+$file-radius: $radius !default
+
+$file-cta-background-color: $scheme-main-ter !default
+$file-cta-color: $text !default
+$file-cta-hover-color: $text-strong !default
+$file-cta-active-color: $text-strong !default
+
+$file-name-border-color: $border !default
+$file-name-border-style: solid !default
+$file-name-border-width: 1px 1px 1px 0 !default
+$file-name-max-width: 16em !default
+
+.file
+ @extend %unselectable
+ align-items: stretch
+ display: flex
+ justify-content: flex-start
+ position: relative
+ // Colors
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ $color-invert: nth($pair, 2)
+ &.is-#{$name}
+ .file-cta
+ background-color: $color
+ border-color: transparent
+ color: $color-invert
+ &:hover,
+ &.is-hovered
+ .file-cta
+ background-color: darken($color, 2.5%)
+ border-color: transparent
+ color: $color-invert
+ &:focus,
+ &.is-focused
+ .file-cta
+ border-color: transparent
+ box-shadow: 0 0 0.5em rgba($color, 0.25)
+ color: $color-invert
+ &:active,
+ &.is-active
+ .file-cta
+ background-color: darken($color, 5%)
+ border-color: transparent
+ color: $color-invert
+ // Sizes
+ &.is-small
+ font-size: $size-small
+ &.is-medium
+ font-size: $size-medium
+ .file-icon
+ .fa
+ font-size: 21px
+ &.is-large
+ font-size: $size-large
+ .file-icon
+ .fa
+ font-size: 28px
+ // Modifiers
+ &.has-name
+ .file-cta
+ border-bottom-right-radius: 0
+ border-top-right-radius: 0
+ .file-name
+ border-bottom-left-radius: 0
+ border-top-left-radius: 0
+ &.is-empty
+ .file-cta
+ border-radius: $file-radius
+ .file-name
+ display: none
+ &.is-boxed
+ .file-label
+ flex-direction: column
+ .file-cta
+ flex-direction: column
+ height: auto
+ padding: 1em 3em
+ .file-name
+ border-width: 0 1px 1px
+ .file-icon
+ height: 1.5em
+ width: 1.5em
+ .fa
+ font-size: 21px
+ &.is-small
+ .file-icon .fa
+ font-size: 14px
+ &.is-medium
+ .file-icon .fa
+ font-size: 28px
+ &.is-large
+ .file-icon .fa
+ font-size: 35px
+ &.has-name
+ .file-cta
+ border-radius: $file-radius $file-radius 0 0
+ .file-name
+ border-radius: 0 0 $file-radius $file-radius
+ border-width: 0 1px 1px
+ &.is-centered
+ justify-content: center
+ &.is-fullwidth
+ .file-label
+ width: 100%
+ .file-name
+ flex-grow: 1
+ max-width: none
+ &.is-right
+ justify-content: flex-end
+ .file-cta
+ border-radius: 0 $file-radius $file-radius 0
+ .file-name
+ border-radius: $file-radius 0 0 $file-radius
+ border-width: 1px 0 1px 1px
+ order: -1
+
+.file-label
+ align-items: stretch
+ display: flex
+ cursor: pointer
+ justify-content: flex-start
+ overflow: hidden
+ position: relative
+ &:hover
+ .file-cta
+ background-color: darken($file-cta-background-color, 2.5%)
+ color: $file-cta-hover-color
+ .file-name
+ border-color: darken($file-name-border-color, 2.5%)
+ &:active
+ .file-cta
+ background-color: darken($file-cta-background-color, 5%)
+ color: $file-cta-active-color
+ .file-name
+ border-color: darken($file-name-border-color, 5%)
+
+.file-input
+ height: 100%
+ left: 0
+ opacity: 0
+ outline: none
+ position: absolute
+ top: 0
+ width: 100%
+
+.file-cta,
+.file-name
+ @extend %control
+ border-color: $file-border-color
+ border-radius: $file-radius
+ font-size: 1em
+ padding-left: 1em
+ padding-right: 1em
+ white-space: nowrap
+
+.file-cta
+ background-color: $file-cta-background-color
+ color: $file-cta-color
+
+.file-name
+ border-color: $file-name-border-color
+ border-style: $file-name-border-style
+ border-width: $file-name-border-width
+ display: block
+ max-width: $file-name-max-width
+ overflow: hidden
+ text-align: left
+ text-overflow: ellipsis
+
+.file-icon
+ align-items: center
+ display: flex
+ height: 1em
+ justify-content: center
+ margin-right: 0.5em
+ width: 1em
+ .fa
+ font-size: 14px
diff --git a/docs/src/assets/bulma/form/input-textarea.sass b/docs/src/assets/bulma/form/input-textarea.sass
new file mode 100644
index 00000000..7636cdae
--- /dev/null
+++ b/docs/src/assets/bulma/form/input-textarea.sass
@@ -0,0 +1,64 @@
+$textarea-padding: $control-padding-horizontal !default
+$textarea-max-height: 40em !default
+$textarea-min-height: 8em !default
+
+%input-textarea
+ @extend %input
+ box-shadow: $input-shadow
+ max-width: 100%
+ width: 100%
+ &[readonly]
+ box-shadow: none
+ // Colors
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ &.is-#{$name}
+ border-color: $color
+ &:focus,
+ &.is-focused,
+ &:active,
+ &.is-active
+ box-shadow: $input-focus-box-shadow-size rgba($color, 0.25)
+ // Sizes
+ &.is-small
+ +control-small
+ &.is-medium
+ +control-medium
+ &.is-large
+ +control-large
+ // Modifiers
+ &.is-fullwidth
+ display: block
+ width: 100%
+ &.is-inline
+ display: inline
+ width: auto
+
+.input
+ @extend %input-textarea
+ &.is-rounded
+ border-radius: $radius-rounded
+ padding-left: calc(#{$control-padding-horizontal} + 0.375em)
+ padding-right: calc(#{$control-padding-horizontal} + 0.375em)
+ &.is-static
+ background-color: transparent
+ border-color: transparent
+ box-shadow: none
+ padding-left: 0
+ padding-right: 0
+
+.textarea
+ @extend %input-textarea
+ display: block
+ max-width: 100%
+ min-width: 100%
+ padding: $textarea-padding
+ resize: vertical
+ &:not([rows])
+ max-height: $textarea-max-height
+ min-height: $textarea-min-height
+ &[rows]
+ height: initial
+ // Modifiers
+ &.has-fixed-size
+ resize: none
diff --git a/docs/src/assets/bulma/form/select.sass b/docs/src/assets/bulma/form/select.sass
new file mode 100644
index 00000000..909b9d52
--- /dev/null
+++ b/docs/src/assets/bulma/form/select.sass
@@ -0,0 +1,85 @@
+.select
+ display: inline-block
+ max-width: 100%
+ position: relative
+ vertical-align: top
+ &:not(.is-multiple)
+ height: $input-height
+ &:not(.is-multiple):not(.is-loading)
+ &::after
+ @extend %arrow
+ border-color: $input-arrow
+ right: 1.125em
+ z-index: 4
+ &.is-rounded
+ select
+ border-radius: $radius-rounded
+ padding-left: 1em
+ select
+ @extend %input
+ cursor: pointer
+ display: block
+ font-size: 1em
+ max-width: 100%
+ outline: none
+ &::-ms-expand
+ display: none
+ &[disabled]:hover,
+ fieldset[disabled] &:hover
+ border-color: $input-disabled-border-color
+ &:not([multiple])
+ padding-right: 2.5em
+ &[multiple]
+ height: auto
+ padding: 0
+ option
+ padding: 0.5em 1em
+ // States
+ &:not(.is-multiple):not(.is-loading):hover
+ &::after
+ border-color: $input-hover-color
+ // Colors
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ &.is-#{$name}
+ &:not(:hover)::after
+ border-color: $color
+ select
+ border-color: $color
+ &:hover,
+ &.is-hovered
+ border-color: darken($color, 5%)
+ &:focus,
+ &.is-focused,
+ &:active,
+ &.is-active
+ box-shadow: $input-focus-box-shadow-size rgba($color, 0.25)
+ // Sizes
+ &.is-small
+ +control-small
+ &.is-medium
+ +control-medium
+ &.is-large
+ +control-large
+ // Modifiers
+ &.is-disabled
+ &::after
+ border-color: $input-disabled-color
+ &.is-fullwidth
+ width: 100%
+ select
+ width: 100%
+ &.is-loading
+ &::after
+ @extend %loader
+ margin-top: 0
+ position: absolute
+ right: 0.625em
+ top: 0.625em
+ transform: none
+ &.is-small:after
+ font-size: $size-small
+ &.is-medium:after
+ font-size: $size-medium
+ &.is-large:after
+ font-size: $size-large
diff --git a/docs/src/assets/bulma/form/shared.sass b/docs/src/assets/bulma/form/shared.sass
new file mode 100644
index 00000000..3005587e
--- /dev/null
+++ b/docs/src/assets/bulma/form/shared.sass
@@ -0,0 +1,55 @@
+$input-color: inherit !default
+$input-background-color: $scheme-main !default
+$input-border-color: $border !default
+$input-height: $control-height !default
+$input-shadow: inset 0 0.0625em 0.125em rgba($scheme-invert, 0.05) !default
+$input-placeholder-color: bulmaRgba($input-color, 0.3) !default
+
+$input-hover-color: $text-strong !default
+$input-hover-border-color: $border-hover !default
+
+$input-focus-color: $text-strong !default
+$input-focus-border-color: $link !default
+$input-focus-box-shadow-size: 0 0 0 0.125em !default
+$input-focus-box-shadow-color: bulmaRgba($link, 0.25) !default
+
+$input-disabled-color: $text-light !default
+$input-disabled-background-color: $background !default
+$input-disabled-border-color: $background !default
+$input-disabled-placeholder-color: bulmaRgba($input-disabled-color, 0.3) !default
+
+$input-arrow: $link !default
+
+$input-icon-color: $border !default
+$input-icon-active-color: $text !default
+
+$input-radius: $radius !default
+
+=input
+ @extend %control
+ background-color: $input-background-color
+ border-color: $input-border-color
+ border-radius: $input-radius
+ color: $input-color
+ +placeholder
+ color: $input-placeholder-color
+ &:hover,
+ &.is-hovered
+ border-color: $input-hover-border-color
+ &:focus,
+ &.is-focused,
+ &:active,
+ &.is-active
+ border-color: $input-focus-border-color
+ box-shadow: $input-focus-box-shadow-size $input-focus-box-shadow-color
+ &[disabled],
+ fieldset[disabled] &
+ background-color: $input-disabled-background-color
+ border-color: $input-disabled-border-color
+ box-shadow: none
+ color: $input-disabled-color
+ +placeholder
+ color: $input-disabled-placeholder-color
+
+%input
+ +input
diff --git a/docs/src/assets/bulma/form/tools.sass b/docs/src/assets/bulma/form/tools.sass
new file mode 100644
index 00000000..ff2f8e20
--- /dev/null
+++ b/docs/src/assets/bulma/form/tools.sass
@@ -0,0 +1,205 @@
+$label-color: $text-strong !default
+$label-weight: $weight-bold !default
+
+$help-size: $size-small !default
+
+.label
+ color: $label-color
+ display: block
+ font-size: $size-normal
+ font-weight: $label-weight
+ &:not(:last-child)
+ margin-bottom: 0.5em
+ // Sizes
+ &.is-small
+ font-size: $size-small
+ &.is-medium
+ font-size: $size-medium
+ &.is-large
+ font-size: $size-large
+
+.help
+ display: block
+ font-size: $help-size
+ margin-top: 0.25rem
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ &.is-#{$name}
+ color: $color
+
+// Containers
+
+.field
+ &:not(:last-child)
+ margin-bottom: 0.75rem
+ // Modifiers
+ &.has-addons
+ display: flex
+ justify-content: flex-start
+ .control
+ &:not(:last-child)
+ margin-right: -1px
+ &:not(:first-child):not(:last-child)
+ .button,
+ .input,
+ .select select
+ border-radius: 0
+ &:first-child:not(:only-child)
+ .button,
+ .input,
+ .select select
+ border-bottom-right-radius: 0
+ border-top-right-radius: 0
+ &:last-child:not(:only-child)
+ .button,
+ .input,
+ .select select
+ border-bottom-left-radius: 0
+ border-top-left-radius: 0
+ .button,
+ .input,
+ .select select
+ &:not([disabled])
+ &:hover,
+ &.is-hovered
+ z-index: 2
+ &:focus,
+ &.is-focused,
+ &:active,
+ &.is-active
+ z-index: 3
+ &:hover
+ z-index: 4
+ &.is-expanded
+ flex-grow: 1
+ flex-shrink: 1
+ &.has-addons-centered
+ justify-content: center
+ &.has-addons-right
+ justify-content: flex-end
+ &.has-addons-fullwidth
+ .control
+ flex-grow: 1
+ flex-shrink: 0
+ &.is-grouped
+ display: flex
+ justify-content: flex-start
+ & > .control
+ flex-shrink: 0
+ &:not(:last-child)
+ margin-bottom: 0
+ margin-right: 0.75rem
+ &.is-expanded
+ flex-grow: 1
+ flex-shrink: 1
+ &.is-grouped-centered
+ justify-content: center
+ &.is-grouped-right
+ justify-content: flex-end
+ &.is-grouped-multiline
+ flex-wrap: wrap
+ & > .control
+ &:last-child,
+ &:not(:last-child)
+ margin-bottom: 0.75rem
+ &:last-child
+ margin-bottom: -0.75rem
+ &:not(:last-child)
+ margin-bottom: 0
+ &.is-horizontal
+ +tablet
+ display: flex
+
+.field-label
+ .label
+ font-size: inherit
+ +mobile
+ margin-bottom: 0.5rem
+ +tablet
+ flex-basis: 0
+ flex-grow: 1
+ flex-shrink: 0
+ margin-right: 1.5rem
+ text-align: right
+ &.is-small
+ font-size: $size-small
+ padding-top: 0.375em
+ &.is-normal
+ padding-top: 0.375em
+ &.is-medium
+ font-size: $size-medium
+ padding-top: 0.375em
+ &.is-large
+ font-size: $size-large
+ padding-top: 0.375em
+
+.field-body
+ .field .field
+ margin-bottom: 0
+ +tablet
+ display: flex
+ flex-basis: 0
+ flex-grow: 5
+ flex-shrink: 1
+ .field
+ margin-bottom: 0
+ & > .field
+ flex-shrink: 1
+ &:not(.is-narrow)
+ flex-grow: 1
+ &:not(:last-child)
+ margin-right: 0.75rem
+
+.control
+ box-sizing: border-box
+ clear: both
+ font-size: $size-normal
+ position: relative
+ text-align: left
+ // Modifiers
+ &.has-icons-left,
+ &.has-icons-right
+ .input,
+ .select
+ &:focus
+ & ~ .icon
+ color: $input-icon-active-color
+ &.is-small ~ .icon
+ font-size: $size-small
+ &.is-medium ~ .icon
+ font-size: $size-medium
+ &.is-large ~ .icon
+ font-size: $size-large
+ .icon
+ color: $input-icon-color
+ height: $input-height
+ pointer-events: none
+ position: absolute
+ top: 0
+ width: $input-height
+ z-index: 4
+ &.has-icons-left
+ .input,
+ .select select
+ padding-left: $input-height
+ .icon.is-left
+ left: 0
+ &.has-icons-right
+ .input,
+ .select select
+ padding-right: $input-height
+ .icon.is-right
+ right: 0
+ &.is-loading
+ &::after
+ @extend %loader
+ position: absolute !important
+ right: 0.625em
+ top: 0.625em
+ z-index: 4
+ &.is-small:after
+ font-size: $size-small
+ &.is-medium:after
+ font-size: $size-medium
+ &.is-large:after
+ font-size: $size-large
diff --git a/docs/src/assets/bulma/grid/_all.sass b/docs/src/assets/bulma/grid/_all.sass
new file mode 100644
index 00000000..e53070f6
--- /dev/null
+++ b/docs/src/assets/bulma/grid/_all.sass
@@ -0,0 +1,4 @@
+@charset "utf-8"
+
+@import "columns.sass"
+@import "tiles.sass"
diff --git a/docs/src/assets/bulma/grid/columns.sass b/docs/src/assets/bulma/grid/columns.sass
new file mode 100644
index 00000000..34a83533
--- /dev/null
+++ b/docs/src/assets/bulma/grid/columns.sass
@@ -0,0 +1,504 @@
+$column-gap: 0.75rem !default
+
+.column
+ display: block
+ flex-basis: 0
+ flex-grow: 1
+ flex-shrink: 1
+ padding: $column-gap
+ .columns.is-mobile > &.is-narrow
+ flex: none
+ .columns.is-mobile > &.is-full
+ flex: none
+ width: 100%
+ .columns.is-mobile > &.is-three-quarters
+ flex: none
+ width: 75%
+ .columns.is-mobile > &.is-two-thirds
+ flex: none
+ width: 66.6666%
+ .columns.is-mobile > &.is-half
+ flex: none
+ width: 50%
+ .columns.is-mobile > &.is-one-third
+ flex: none
+ width: 33.3333%
+ .columns.is-mobile > &.is-one-quarter
+ flex: none
+ width: 25%
+ .columns.is-mobile > &.is-one-fifth
+ flex: none
+ width: 20%
+ .columns.is-mobile > &.is-two-fifths
+ flex: none
+ width: 40%
+ .columns.is-mobile > &.is-three-fifths
+ flex: none
+ width: 60%
+ .columns.is-mobile > &.is-four-fifths
+ flex: none
+ width: 80%
+ .columns.is-mobile > &.is-offset-three-quarters
+ margin-left: 75%
+ .columns.is-mobile > &.is-offset-two-thirds
+ margin-left: 66.6666%
+ .columns.is-mobile > &.is-offset-half
+ margin-left: 50%
+ .columns.is-mobile > &.is-offset-one-third
+ margin-left: 33.3333%
+ .columns.is-mobile > &.is-offset-one-quarter
+ margin-left: 25%
+ .columns.is-mobile > &.is-offset-one-fifth
+ margin-left: 20%
+ .columns.is-mobile > &.is-offset-two-fifths
+ margin-left: 40%
+ .columns.is-mobile > &.is-offset-three-fifths
+ margin-left: 60%
+ .columns.is-mobile > &.is-offset-four-fifths
+ margin-left: 80%
+ @for $i from 0 through 12
+ .columns.is-mobile > &.is-#{$i}
+ flex: none
+ width: percentage($i / 12)
+ .columns.is-mobile > &.is-offset-#{$i}
+ margin-left: percentage($i / 12)
+ +mobile
+ &.is-narrow-mobile
+ flex: none
+ &.is-full-mobile
+ flex: none
+ width: 100%
+ &.is-three-quarters-mobile
+ flex: none
+ width: 75%
+ &.is-two-thirds-mobile
+ flex: none
+ width: 66.6666%
+ &.is-half-mobile
+ flex: none
+ width: 50%
+ &.is-one-third-mobile
+ flex: none
+ width: 33.3333%
+ &.is-one-quarter-mobile
+ flex: none
+ width: 25%
+ &.is-one-fifth-mobile
+ flex: none
+ width: 20%
+ &.is-two-fifths-mobile
+ flex: none
+ width: 40%
+ &.is-three-fifths-mobile
+ flex: none
+ width: 60%
+ &.is-four-fifths-mobile
+ flex: none
+ width: 80%
+ &.is-offset-three-quarters-mobile
+ margin-left: 75%
+ &.is-offset-two-thirds-mobile
+ margin-left: 66.6666%
+ &.is-offset-half-mobile
+ margin-left: 50%
+ &.is-offset-one-third-mobile
+ margin-left: 33.3333%
+ &.is-offset-one-quarter-mobile
+ margin-left: 25%
+ &.is-offset-one-fifth-mobile
+ margin-left: 20%
+ &.is-offset-two-fifths-mobile
+ margin-left: 40%
+ &.is-offset-three-fifths-mobile
+ margin-left: 60%
+ &.is-offset-four-fifths-mobile
+ margin-left: 80%
+ @for $i from 0 through 12
+ &.is-#{$i}-mobile
+ flex: none
+ width: percentage($i / 12)
+ &.is-offset-#{$i}-mobile
+ margin-left: percentage($i / 12)
+ +tablet
+ &.is-narrow,
+ &.is-narrow-tablet
+ flex: none
+ &.is-full,
+ &.is-full-tablet
+ flex: none
+ width: 100%
+ &.is-three-quarters,
+ &.is-three-quarters-tablet
+ flex: none
+ width: 75%
+ &.is-two-thirds,
+ &.is-two-thirds-tablet
+ flex: none
+ width: 66.6666%
+ &.is-half,
+ &.is-half-tablet
+ flex: none
+ width: 50%
+ &.is-one-third,
+ &.is-one-third-tablet
+ flex: none
+ width: 33.3333%
+ &.is-one-quarter,
+ &.is-one-quarter-tablet
+ flex: none
+ width: 25%
+ &.is-one-fifth,
+ &.is-one-fifth-tablet
+ flex: none
+ width: 20%
+ &.is-two-fifths,
+ &.is-two-fifths-tablet
+ flex: none
+ width: 40%
+ &.is-three-fifths,
+ &.is-three-fifths-tablet
+ flex: none
+ width: 60%
+ &.is-four-fifths,
+ &.is-four-fifths-tablet
+ flex: none
+ width: 80%
+ &.is-offset-three-quarters,
+ &.is-offset-three-quarters-tablet
+ margin-left: 75%
+ &.is-offset-two-thirds,
+ &.is-offset-two-thirds-tablet
+ margin-left: 66.6666%
+ &.is-offset-half,
+ &.is-offset-half-tablet
+ margin-left: 50%
+ &.is-offset-one-third,
+ &.is-offset-one-third-tablet
+ margin-left: 33.3333%
+ &.is-offset-one-quarter,
+ &.is-offset-one-quarter-tablet
+ margin-left: 25%
+ &.is-offset-one-fifth,
+ &.is-offset-one-fifth-tablet
+ margin-left: 20%
+ &.is-offset-two-fifths,
+ &.is-offset-two-fifths-tablet
+ margin-left: 40%
+ &.is-offset-three-fifths,
+ &.is-offset-three-fifths-tablet
+ margin-left: 60%
+ &.is-offset-four-fifths,
+ &.is-offset-four-fifths-tablet
+ margin-left: 80%
+ @for $i from 0 through 12
+ &.is-#{$i},
+ &.is-#{$i}-tablet
+ flex: none
+ width: percentage($i / 12)
+ &.is-offset-#{$i},
+ &.is-offset-#{$i}-tablet
+ margin-left: percentage($i / 12)
+ +touch
+ &.is-narrow-touch
+ flex: none
+ &.is-full-touch
+ flex: none
+ width: 100%
+ &.is-three-quarters-touch
+ flex: none
+ width: 75%
+ &.is-two-thirds-touch
+ flex: none
+ width: 66.6666%
+ &.is-half-touch
+ flex: none
+ width: 50%
+ &.is-one-third-touch
+ flex: none
+ width: 33.3333%
+ &.is-one-quarter-touch
+ flex: none
+ width: 25%
+ &.is-one-fifth-touch
+ flex: none
+ width: 20%
+ &.is-two-fifths-touch
+ flex: none
+ width: 40%
+ &.is-three-fifths-touch
+ flex: none
+ width: 60%
+ &.is-four-fifths-touch
+ flex: none
+ width: 80%
+ &.is-offset-three-quarters-touch
+ margin-left: 75%
+ &.is-offset-two-thirds-touch
+ margin-left: 66.6666%
+ &.is-offset-half-touch
+ margin-left: 50%
+ &.is-offset-one-third-touch
+ margin-left: 33.3333%
+ &.is-offset-one-quarter-touch
+ margin-left: 25%
+ &.is-offset-one-fifth-touch
+ margin-left: 20%
+ &.is-offset-two-fifths-touch
+ margin-left: 40%
+ &.is-offset-three-fifths-touch
+ margin-left: 60%
+ &.is-offset-four-fifths-touch
+ margin-left: 80%
+ @for $i from 0 through 12
+ &.is-#{$i}-touch
+ flex: none
+ width: percentage($i / 12)
+ &.is-offset-#{$i}-touch
+ margin-left: percentage($i / 12)
+ +desktop
+ &.is-narrow-desktop
+ flex: none
+ &.is-full-desktop
+ flex: none
+ width: 100%
+ &.is-three-quarters-desktop
+ flex: none
+ width: 75%
+ &.is-two-thirds-desktop
+ flex: none
+ width: 66.6666%
+ &.is-half-desktop
+ flex: none
+ width: 50%
+ &.is-one-third-desktop
+ flex: none
+ width: 33.3333%
+ &.is-one-quarter-desktop
+ flex: none
+ width: 25%
+ &.is-one-fifth-desktop
+ flex: none
+ width: 20%
+ &.is-two-fifths-desktop
+ flex: none
+ width: 40%
+ &.is-three-fifths-desktop
+ flex: none
+ width: 60%
+ &.is-four-fifths-desktop
+ flex: none
+ width: 80%
+ &.is-offset-three-quarters-desktop
+ margin-left: 75%
+ &.is-offset-two-thirds-desktop
+ margin-left: 66.6666%
+ &.is-offset-half-desktop
+ margin-left: 50%
+ &.is-offset-one-third-desktop
+ margin-left: 33.3333%
+ &.is-offset-one-quarter-desktop
+ margin-left: 25%
+ &.is-offset-one-fifth-desktop
+ margin-left: 20%
+ &.is-offset-two-fifths-desktop
+ margin-left: 40%
+ &.is-offset-three-fifths-desktop
+ margin-left: 60%
+ &.is-offset-four-fifths-desktop
+ margin-left: 80%
+ @for $i from 0 through 12
+ &.is-#{$i}-desktop
+ flex: none
+ width: percentage($i / 12)
+ &.is-offset-#{$i}-desktop
+ margin-left: percentage($i / 12)
+ +widescreen
+ &.is-narrow-widescreen
+ flex: none
+ &.is-full-widescreen
+ flex: none
+ width: 100%
+ &.is-three-quarters-widescreen
+ flex: none
+ width: 75%
+ &.is-two-thirds-widescreen
+ flex: none
+ width: 66.6666%
+ &.is-half-widescreen
+ flex: none
+ width: 50%
+ &.is-one-third-widescreen
+ flex: none
+ width: 33.3333%
+ &.is-one-quarter-widescreen
+ flex: none
+ width: 25%
+ &.is-one-fifth-widescreen
+ flex: none
+ width: 20%
+ &.is-two-fifths-widescreen
+ flex: none
+ width: 40%
+ &.is-three-fifths-widescreen
+ flex: none
+ width: 60%
+ &.is-four-fifths-widescreen
+ flex: none
+ width: 80%
+ &.is-offset-three-quarters-widescreen
+ margin-left: 75%
+ &.is-offset-two-thirds-widescreen
+ margin-left: 66.6666%
+ &.is-offset-half-widescreen
+ margin-left: 50%
+ &.is-offset-one-third-widescreen
+ margin-left: 33.3333%
+ &.is-offset-one-quarter-widescreen
+ margin-left: 25%
+ &.is-offset-one-fifth-widescreen
+ margin-left: 20%
+ &.is-offset-two-fifths-widescreen
+ margin-left: 40%
+ &.is-offset-three-fifths-widescreen
+ margin-left: 60%
+ &.is-offset-four-fifths-widescreen
+ margin-left: 80%
+ @for $i from 0 through 12
+ &.is-#{$i}-widescreen
+ flex: none
+ width: percentage($i / 12)
+ &.is-offset-#{$i}-widescreen
+ margin-left: percentage($i / 12)
+ +fullhd
+ &.is-narrow-fullhd
+ flex: none
+ &.is-full-fullhd
+ flex: none
+ width: 100%
+ &.is-three-quarters-fullhd
+ flex: none
+ width: 75%
+ &.is-two-thirds-fullhd
+ flex: none
+ width: 66.6666%
+ &.is-half-fullhd
+ flex: none
+ width: 50%
+ &.is-one-third-fullhd
+ flex: none
+ width: 33.3333%
+ &.is-one-quarter-fullhd
+ flex: none
+ width: 25%
+ &.is-one-fifth-fullhd
+ flex: none
+ width: 20%
+ &.is-two-fifths-fullhd
+ flex: none
+ width: 40%
+ &.is-three-fifths-fullhd
+ flex: none
+ width: 60%
+ &.is-four-fifths-fullhd
+ flex: none
+ width: 80%
+ &.is-offset-three-quarters-fullhd
+ margin-left: 75%
+ &.is-offset-two-thirds-fullhd
+ margin-left: 66.6666%
+ &.is-offset-half-fullhd
+ margin-left: 50%
+ &.is-offset-one-third-fullhd
+ margin-left: 33.3333%
+ &.is-offset-one-quarter-fullhd
+ margin-left: 25%
+ &.is-offset-one-fifth-fullhd
+ margin-left: 20%
+ &.is-offset-two-fifths-fullhd
+ margin-left: 40%
+ &.is-offset-three-fifths-fullhd
+ margin-left: 60%
+ &.is-offset-four-fifths-fullhd
+ margin-left: 80%
+ @for $i from 0 through 12
+ &.is-#{$i}-fullhd
+ flex: none
+ width: percentage($i / 12)
+ &.is-offset-#{$i}-fullhd
+ margin-left: percentage($i / 12)
+
+.columns
+ margin-left: (-$column-gap)
+ margin-right: (-$column-gap)
+ margin-top: (-$column-gap)
+ &:last-child
+ margin-bottom: (-$column-gap)
+ &:not(:last-child)
+ margin-bottom: calc(1.5rem - #{$column-gap})
+ // Modifiers
+ &.is-centered
+ justify-content: center
+ &.is-gapless
+ margin-left: 0
+ margin-right: 0
+ margin-top: 0
+ & > .column
+ margin: 0
+ padding: 0 !important
+ &:not(:last-child)
+ margin-bottom: 1.5rem
+ &:last-child
+ margin-bottom: 0
+ &.is-mobile
+ display: flex
+ &.is-multiline
+ flex-wrap: wrap
+ &.is-vcentered
+ align-items: center
+ // Responsiveness
+ +tablet
+ &:not(.is-desktop)
+ display: flex
+ +desktop
+ // Modifiers
+ &.is-desktop
+ display: flex
+
+@if $variable-columns
+ .columns.is-variable
+ --columnGap: 0.75rem
+ margin-left: calc(-1 * var(--columnGap))
+ margin-right: calc(-1 * var(--columnGap))
+ .column
+ padding-left: var(--columnGap)
+ padding-right: var(--columnGap)
+ @for $i from 0 through 8
+ &.is-#{$i}
+ --columnGap: #{$i * 0.25rem}
+ +mobile
+ &.is-#{$i}-mobile
+ --columnGap: #{$i * 0.25rem}
+ +tablet
+ &.is-#{$i}-tablet
+ --columnGap: #{$i * 0.25rem}
+ +tablet-only
+ &.is-#{$i}-tablet-only
+ --columnGap: #{$i * 0.25rem}
+ +touch
+ &.is-#{$i}-touch
+ --columnGap: #{$i * 0.25rem}
+ +desktop
+ &.is-#{$i}-desktop
+ --columnGap: #{$i * 0.25rem}
+ +desktop-only
+ &.is-#{$i}-desktop-only
+ --columnGap: #{$i * 0.25rem}
+ +widescreen
+ &.is-#{$i}-widescreen
+ --columnGap: #{$i * 0.25rem}
+ +widescreen-only
+ &.is-#{$i}-widescreen-only
+ --columnGap: #{$i * 0.25rem}
+ +fullhd
+ &.is-#{$i}-fullhd
+ --columnGap: #{$i * 0.25rem}
diff --git a/docs/src/assets/bulma/grid/tiles.sass b/docs/src/assets/bulma/grid/tiles.sass
new file mode 100644
index 00000000..15648c29
--- /dev/null
+++ b/docs/src/assets/bulma/grid/tiles.sass
@@ -0,0 +1,34 @@
+$tile-spacing: 0.75rem !default
+
+.tile
+ align-items: stretch
+ display: block
+ flex-basis: 0
+ flex-grow: 1
+ flex-shrink: 1
+ min-height: min-content
+ // Modifiers
+ &.is-ancestor
+ margin-left: $tile-spacing * -1
+ margin-right: $tile-spacing * -1
+ margin-top: $tile-spacing * -1
+ &:last-child
+ margin-bottom: $tile-spacing * -1
+ &:not(:last-child)
+ margin-bottom: $tile-spacing
+ &.is-child
+ margin: 0 !important
+ &.is-parent
+ padding: $tile-spacing
+ &.is-vertical
+ flex-direction: column
+ & > .tile.is-child:not(:last-child)
+ margin-bottom: 1.5rem !important
+ // Responsiveness
+ +tablet
+ &:not(.is-child)
+ display: flex
+ @for $i from 1 through 12
+ &.is-#{$i}
+ flex: none
+ width: ($i / 12) * 100%
diff --git a/docs/src/assets/bulma/layout/_all.sass b/docs/src/assets/bulma/layout/_all.sass
new file mode 100644
index 00000000..143ada35
--- /dev/null
+++ b/docs/src/assets/bulma/layout/_all.sass
@@ -0,0 +1,5 @@
+@charset "utf-8"
+
+@import "hero.sass"
+@import "section.sass"
+@import "footer.sass"
diff --git a/docs/src/assets/bulma/layout/footer.sass b/docs/src/assets/bulma/layout/footer.sass
new file mode 100644
index 00000000..8faa11ed
--- /dev/null
+++ b/docs/src/assets/bulma/layout/footer.sass
@@ -0,0 +1,9 @@
+$footer-background-color: $scheme-main-bis !default
+$footer-color: false !default
+$footer-padding: 3rem 1.5rem 6rem !default
+
+.footer
+ background-color: $footer-background-color
+ padding: $footer-padding
+ @if $footer-color
+ color: $footer-color
diff --git a/docs/src/assets/bulma/layout/hero.sass b/docs/src/assets/bulma/layout/hero.sass
new file mode 100644
index 00000000..ae8ed086
--- /dev/null
+++ b/docs/src/assets/bulma/layout/hero.sass
@@ -0,0 +1,144 @@
+$hero-body-padding: 3rem 1.5rem !default
+$hero-body-padding-small: 1.5rem !default
+$hero-body-padding-medium: 9rem 1.5rem !default
+$hero-body-padding-large: 18rem 1.5rem !default
+
+// Main container
+.hero
+ align-items: stretch
+ display: flex
+ flex-direction: column
+ justify-content: space-between
+ .navbar
+ background: none
+ .tabs
+ ul
+ border-bottom: none
+ // Colors
+ @each $name, $pair in $colors
+ $color: nth($pair, 1)
+ $color-invert: nth($pair, 2)
+ &.is-#{$name}
+ background-color: $color
+ color: $color-invert
+ a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),
+ strong
+ color: inherit
+ .title
+ color: $color-invert
+ .subtitle
+ color: bulmaRgba($color-invert, 0.9)
+ a:not(.button),
+ strong
+ color: $color-invert
+ .navbar-menu
+ +touch
+ background-color: $color
+ .navbar-item,
+ .navbar-link
+ color: bulmaRgba($color-invert, 0.7)
+ a.navbar-item,
+ .navbar-link
+ &:hover,
+ &.is-active
+ background-color: darken($color, 5%)
+ color: $color-invert
+ .tabs
+ a
+ color: $color-invert
+ opacity: 0.9
+ &:hover
+ opacity: 1
+ li
+ &.is-active a
+ opacity: 1
+ &.is-boxed,
+ &.is-toggle
+ a
+ color: $color-invert
+ &:hover
+ background-color: bulmaRgba($scheme-invert, 0.1)
+ li.is-active a
+ &,
+ &:hover
+ background-color: $color-invert
+ border-color: $color-invert
+ color: $color
+ // Modifiers
+ &.is-bold
+ $gradient-top-left: darken(saturate(adjust-hue($color, -10deg), 10%), 10%)
+ $gradient-bottom-right: lighten(saturate(adjust-hue($color, 10deg), 5%), 5%)
+ background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)
+ +mobile
+ .navbar-menu
+ background-image: linear-gradient(141deg, $gradient-top-left 0%, $color 71%, $gradient-bottom-right 100%)
+ // Sizes
+ &.is-small
+ .hero-body
+ padding: $hero-body-padding-small
+ &.is-medium
+ +tablet
+ .hero-body
+ padding: $hero-body-padding-medium
+ &.is-large
+ +tablet
+ .hero-body
+ padding: $hero-body-padding-large
+ &.is-halfheight,
+ &.is-fullheight,
+ &.is-fullheight-with-navbar
+ .hero-body
+ align-items: center
+ display: flex
+ & > .container
+ flex-grow: 1
+ flex-shrink: 1
+ &.is-halfheight
+ min-height: 50vh
+ &.is-fullheight
+ min-height: 100vh
+
+// Components
+
+.hero-video
+ @extend %overlay
+ overflow: hidden
+ video
+ left: 50%
+ min-height: 100%
+ min-width: 100%
+ position: absolute
+ top: 50%
+ transform: translate3d(-50%, -50%, 0)
+ // Modifiers
+ &.is-transparent
+ opacity: 0.3
+ // Responsiveness
+ +mobile
+ display: none
+
+.hero-buttons
+ margin-top: 1.5rem
+ // Responsiveness
+ +mobile
+ .button
+ display: flex
+ &:not(:last-child)
+ margin-bottom: 0.75rem
+ +tablet
+ display: flex
+ justify-content: center
+ .button:not(:last-child)
+ margin-right: 1.5rem
+
+// Containers
+
+.hero-head,
+.hero-foot
+ flex-grow: 0
+ flex-shrink: 0
+
+.hero-body
+ flex-grow: 1
+ flex-shrink: 0
+ padding: $hero-body-padding
diff --git a/docs/src/assets/bulma/layout/section.sass b/docs/src/assets/bulma/layout/section.sass
new file mode 100644
index 00000000..6f2d3523
--- /dev/null
+++ b/docs/src/assets/bulma/layout/section.sass
@@ -0,0 +1,13 @@
+$section-padding: 3rem 1.5rem !default
+$section-padding-medium: 9rem 1.5rem !default
+$section-padding-large: 18rem 1.5rem !default
+
+.section
+ padding: $section-padding
+ // Responsiveness
+ +desktop
+ // Sizes
+ &.is-medium
+ padding: $section-padding-medium
+ &.is-large
+ padding: $section-padding-large
diff --git a/docs/src/assets/bulma/utilities/_all.sass b/docs/src/assets/bulma/utilities/_all.sass
new file mode 100644
index 00000000..bf4ecfe3
--- /dev/null
+++ b/docs/src/assets/bulma/utilities/_all.sass
@@ -0,0 +1,8 @@
+@charset "utf-8"
+
+@import "initial-variables.sass"
+@import "functions.sass"
+@import "derived-variables.sass"
+@import "animations.sass"
+@import "mixins.sass"
+@import "controls.sass"
diff --git a/docs/src/assets/bulma/utilities/animations.sass b/docs/src/assets/bulma/utilities/animations.sass
new file mode 100644
index 00000000..a14525d7
--- /dev/null
+++ b/docs/src/assets/bulma/utilities/animations.sass
@@ -0,0 +1,5 @@
+@keyframes spinAround
+ from
+ transform: rotate(0deg)
+ to
+ transform: rotate(359deg)
diff --git a/docs/src/assets/bulma/utilities/controls.sass b/docs/src/assets/bulma/utilities/controls.sass
new file mode 100644
index 00000000..cc7672a1
--- /dev/null
+++ b/docs/src/assets/bulma/utilities/controls.sass
@@ -0,0 +1,50 @@
+$control-radius: $radius !default
+$control-radius-small: $radius-small !default
+
+$control-border-width: 1px !default
+
+$control-height: 2.5em !default
+$control-line-height: 1.5 !default
+
+$control-padding-vertical: calc(0.5em - #{$control-border-width}) !default
+$control-padding-horizontal: calc(0.75em - #{$control-border-width}) !default
+
+=control
+ -moz-appearance: none
+ -webkit-appearance: none
+ align-items: center
+ border: $control-border-width solid transparent
+ border-radius: $control-radius
+ box-shadow: none
+ display: inline-flex
+ font-size: $size-normal
+ height: $control-height
+ justify-content: flex-start
+ line-height: $control-line-height
+ padding-bottom: $control-padding-vertical
+ padding-left: $control-padding-horizontal
+ padding-right: $control-padding-horizontal
+ padding-top: $control-padding-vertical
+ position: relative
+ vertical-align: top
+ // States
+ &:focus,
+ &.is-focused,
+ &:active,
+ &.is-active
+ outline: none
+ &[disabled],
+ fieldset[disabled] &
+ cursor: not-allowed
+
+%control
+ +control
+
+// The controls sizes use mixins so they can be used at different breakpoints
+=control-small
+ border-radius: $control-radius-small
+ font-size: $size-small
+=control-medium
+ font-size: $size-medium
+=control-large
+ font-size: $size-large
diff --git a/docs/src/assets/bulma/utilities/derived-variables.sass b/docs/src/assets/bulma/utilities/derived-variables.sass
new file mode 100644
index 00000000..18a7f461
--- /dev/null
+++ b/docs/src/assets/bulma/utilities/derived-variables.sass
@@ -0,0 +1,106 @@
+$primary: $turquoise !default
+
+$info: $cyan !default
+$success: $green !default
+$warning: $yellow !default
+$danger: $red !default
+
+$light: $white-ter !default
+$dark: $grey-darker !default
+
+// Invert colors
+
+$orange-invert: findColorInvert($orange) !default
+$yellow-invert: findColorInvert($yellow) !default
+$green-invert: findColorInvert($green) !default
+$turquoise-invert: findColorInvert($turquoise) !default
+$cyan-invert: findColorInvert($cyan) !default
+$blue-invert: findColorInvert($blue) !default
+$purple-invert: findColorInvert($purple) !default
+$red-invert: findColorInvert($red) !default
+
+$primary-invert: findColorInvert($primary) !default
+$primary-light: findLightColor($primary) !default
+$primary-dark: findDarkColor($primary) !default
+$info-invert: findColorInvert($info) !default
+$info-light: findLightColor($info) !default
+$info-dark: findDarkColor($info) !default
+$success-invert: findColorInvert($success) !default
+$success-light: findLightColor($success) !default
+$success-dark: findDarkColor($success) !default
+$warning-invert: findColorInvert($warning) !default
+$warning-light: findLightColor($warning) !default
+$warning-dark: findDarkColor($warning) !default
+$danger-invert: findColorInvert($danger) !default
+$danger-light: findLightColor($danger) !default
+$danger-dark: findDarkColor($danger) !default
+$light-invert: findColorInvert($light) !default
+$dark-invert: findColorInvert($dark) !default
+
+// General colors
+
+$scheme-main: $white !default
+$scheme-main-bis: $white-bis !default
+$scheme-main-ter: $white-ter !default
+$scheme-invert: $black !default
+$scheme-invert-bis: $black-bis !default
+$scheme-invert-ter: $black-ter !default
+
+$background: $white-ter !default
+
+$border: $grey-lighter !default
+$border-hover: $grey-light !default
+$border-light: $grey-lightest !default
+$border-light-hover: $grey-light !default
+
+// Text colors
+
+$text: $grey-dark !default
+$text-invert: findColorInvert($text) !default
+$text-light: $grey !default
+$text-strong: $grey-darker !default
+
+// Code colors
+
+$code: $red !default
+$code-background: $background !default
+
+$pre: $text !default
+$pre-background: $background !default
+
+// Link colors
+
+$link: $blue !default
+$link-invert: findColorInvert($link) !default
+$link-light: findLightColor($link) !default
+$link-dark: findDarkColor($link) !default
+$link-visited: $purple !default
+
+$link-hover: $grey-darker !default
+$link-hover-border: $grey-light !default
+
+$link-focus: $grey-darker !default
+$link-focus-border: $blue !default
+
+$link-active: $grey-darker !default
+$link-active-border: $grey-dark !default
+
+// Typography
+
+$family-primary: $family-sans-serif !default
+$family-secondary: $family-sans-serif !default
+$family-code: $family-monospace !default
+
+$size-small: $size-7 !default
+$size-normal: $size-6 !default
+$size-medium: $size-5 !default
+$size-large: $size-4 !default
+
+// Lists and maps
+$custom-colors: null !default
+$custom-shades: null !default
+
+$colors: mergeColorMaps(("white": ($white, $black), "black": ($black, $white), "light": ($light, $light-invert), "dark": ($dark, $dark-invert), "primary": ($primary, $primary-invert, $primary-light, $primary-dark), "link": ($link, $link-invert, $link-light, $link-dark), "info": ($info, $info-invert, $info-light, $info-dark), "success": ($success, $success-invert, $success-light, $success-dark), "warning": ($warning, $warning-invert, $warning-light, $warning-dark), "danger": ($danger, $danger-invert, $danger-light, $danger-dark)), $custom-colors) !default
+$shades: mergeColorMaps(("black-bis": $black-bis, "black-ter": $black-ter, "grey-darker": $grey-darker, "grey-dark": $grey-dark, "grey": $grey, "grey-light": $grey-light, "grey-lighter": $grey-lighter, "white-ter": $white-ter, "white-bis": $white-bis), $custom-shades) !default
+
+$sizes: $size-1 $size-2 $size-3 $size-4 $size-5 $size-6 $size-7 !default
diff --git a/docs/src/assets/bulma/utilities/functions.sass b/docs/src/assets/bulma/utilities/functions.sass
new file mode 100644
index 00000000..54225e21
--- /dev/null
+++ b/docs/src/assets/bulma/utilities/functions.sass
@@ -0,0 +1,103 @@
+@function mergeColorMaps($bulma-colors, $custom-colors)
+ // We return at least Bulma's hard-coded colors
+ $merged-colors: $bulma-colors
+
+ // We want a map as input
+ @if type-of($custom-colors) == 'map'
+ @each $name, $components in $custom-colors
+ // The color name should be a string
+ // and the components either a single color
+ // or a colors list with at least one element
+ @if type-of($name) == 'string' and (type-of($components) == 'list' or type-of($components) == 'color') and length($components) >= 1
+ $color-base: null
+ $color-invert: null
+ $color-light: null
+ $color-dark: null
+ $value: null
+
+ // The param can either be a single color
+ // or a list of 2 colors
+ @if type-of($components) == 'color'
+ $color-base: $components
+ $color-invert: findColorInvert($color-base)
+ $color-light: findLightColor($color-base)
+ $color-dark: findDarkColor($color-base)
+ @else if type-of($components) == 'list'
+ $color-base: nth($components, 1)
+ // If Invert, Light and Dark are provided
+ @if length($components) > 3
+ $color-invert: nth($components, 2)
+ $color-light: nth($components, 3)
+ $color-dark: nth($components, 4)
+ // If only Invert and Light are provided
+ @else if length($components) > 2
+ $color-invert: nth($components, 2)
+ $color-light: nth($components, 3)
+ $color-dark: findDarkColor($color-base)
+ // If only Invert is provided
+ @else
+ $color-invert: nth($components, 2)
+ $color-light: findLightColor($color-base)
+ $color-dark: findDarkColor($color-base)
+
+ $value: ($color-base, $color-invert, $color-light, $color-dark)
+
+ // We only want to merge the map if the color base is an actual color
+ @if type-of($color-base) == 'color'
+ // We merge this colors elements as map with Bulma's colors map
+ // (we can override them this way, no multiple definition for the same name)
+ // $merged-colors: map_merge($merged-colors, ($name: ($color-base, $color-invert, $color-light, $color-dark)))
+ $merged-colors: map_merge($merged-colors, ($name: $value))
+
+ @return $merged-colors
+
+@function powerNumber($number, $exp)
+ $value: 1
+ @if $exp > 0
+ @for $i from 1 through $exp
+ $value: $value * $number
+ @else if $exp < 0
+ @for $i from 1 through -$exp
+ $value: $value / $number
+ @return $value
+
+@function colorLuminance($color)
+ $color-rgb: ('red': red($color),'green': green($color),'blue': blue($color))
+ @each $name, $value in $color-rgb
+ $adjusted: 0
+ $value: $value / 255
+ @if $value < 0.03928
+ $value: $value / 12.92
+ @else
+ $value: ($value + .055) / 1.055
+ $value: powerNumber($value, 2)
+ $color-rgb: map-merge($color-rgb, ($name: $value))
+ @return (map-get($color-rgb, 'red') * .2126) + (map-get($color-rgb, 'green') * .7152) + (map-get($color-rgb, 'blue') * .0722)
+
+@function findColorInvert($color)
+ @if (colorLuminance($color) > 0.55)
+ @return rgba(#000, 0.7)
+ @else
+ @return #fff
+
+@function findLightColor($color)
+ @if type-of($color) == 'color'
+ $l: 96%
+ @if lightness($color) > 96%
+ $l: lightness($color)
+ @return change-color($color, $lightness: $l)
+ @return $background
+
+@function findDarkColor($color)
+ @if type-of($color) == 'color'
+ $base-l: 29%
+ $luminance: colorLuminance($color)
+ $luminance-delta: (0.53 - $luminance)
+ $target-l: round($base-l + ($luminance-delta * 53))
+ @return change-color($color, $lightness: max($base-l, $target-l))
+ @return $text-strong
+
+@function bulmaRgba($color, $alpha)
+ @if type-of($color) == 'color'
+ @return rgba($color, $alpha)
+ @return $color
diff --git a/docs/src/assets/bulma/utilities/initial-variables.sass b/docs/src/assets/bulma/utilities/initial-variables.sass
new file mode 100644
index 00000000..03bbc128
--- /dev/null
+++ b/docs/src/assets/bulma/utilities/initial-variables.sass
@@ -0,0 +1,77 @@
+// Colors
+
+$black: hsl(0, 0%, 4%) !default
+$black-bis: hsl(0, 0%, 7%) !default
+$black-ter: hsl(0, 0%, 14%) !default
+
+$grey-darker: hsl(0, 0%, 21%) !default
+$grey-dark: hsl(0, 0%, 29%) !default
+$grey: hsl(0, 0%, 48%) !default
+$grey-light: hsl(0, 0%, 71%) !default
+$grey-lighter: hsl(0, 0%, 86%) !default
+$grey-lightest: hsl(0, 0%, 93%) !default
+
+$white-ter: hsl(0, 0%, 96%) !default
+$white-bis: hsl(0, 0%, 98%) !default
+$white: hsl(0, 0%, 100%) !default
+
+$orange: hsl(14, 100%, 53%) !default
+$yellow: hsl(48, 100%, 67%) !default
+$green: hsl(141, 53%, 53%) !default
+$turquoise: hsl(171, 100%, 41%) !default
+$cyan: hsl(204, 71%, 53%) !default
+$blue: hsl(217, 71%, 53%) !default
+$purple: hsl(271, 100%, 71%) !default
+$red: hsl(348, 86%, 61%) !default
+
+// Typography
+
+$family-sans-serif: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif !default
+$family-monospace: monospace !default
+$render-mode: optimizeLegibility !default
+
+$size-1: 3rem !default
+$size-2: 2.5rem !default
+$size-3: 2rem !default
+$size-4: 1.5rem !default
+$size-5: 1.25rem !default
+$size-6: 1rem !default
+$size-7: 0.75rem !default
+
+$weight-light: 300 !default
+$weight-normal: 400 !default
+$weight-medium: 500 !default
+$weight-semibold: 600 !default
+$weight-bold: 700 !default
+
+// Spacing
+
+$block-spacing: 1.5rem !default
+
+// Responsiveness
+
+// The container horizontal gap, which acts as the offset for breakpoints
+$gap: 32px !default
+// 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16
+$tablet: 769px !default
+// 960px container + 4rem
+$desktop: 960px + (2 * $gap) !default
+// 1152px container + 4rem
+$widescreen: 1152px + (2 * $gap) !default
+$widescreen-enabled: true !default
+// 1344px container + 4rem
+$fullhd: 1344px + (2 * $gap) !default
+$fullhd-enabled: true !default
+
+// Miscellaneous
+
+$easing: ease-out !default
+$radius-small: 2px !default
+$radius: 4px !default
+$radius-large: 6px !default
+$radius-rounded: 290486px !default
+$speed: 86ms !default
+
+// Flags
+
+$variable-columns: true !default
diff --git a/docs/src/assets/bulma/utilities/mixins.sass b/docs/src/assets/bulma/utilities/mixins.sass
new file mode 100644
index 00000000..27d74673
--- /dev/null
+++ b/docs/src/assets/bulma/utilities/mixins.sass
@@ -0,0 +1,261 @@
+@import "initial-variables"
+
+=clearfix
+ &::after
+ clear: both
+ content: " "
+ display: table
+
+=center($width, $height: 0)
+ position: absolute
+ @if $height != 0
+ left: calc(50% - (#{$width} / 2))
+ top: calc(50% - (#{$height} / 2))
+ @else
+ left: calc(50% - (#{$width} / 2))
+ top: calc(50% - (#{$width} / 2))
+
+=fa($size, $dimensions)
+ display: inline-block
+ font-size: $size
+ height: $dimensions
+ line-height: $dimensions
+ text-align: center
+ vertical-align: top
+ width: $dimensions
+
+=hamburger($dimensions)
+ cursor: pointer
+ display: block
+ height: $dimensions
+ position: relative
+ width: $dimensions
+ span
+ background-color: currentColor
+ display: block
+ height: 1px
+ left: calc(50% - 8px)
+ position: absolute
+ transform-origin: center
+ transition-duration: $speed
+ transition-property: background-color, opacity, transform
+ transition-timing-function: $easing
+ width: 16px
+ &:nth-child(1)
+ top: calc(50% - 6px)
+ &:nth-child(2)
+ top: calc(50% - 1px)
+ &:nth-child(3)
+ top: calc(50% + 4px)
+ &:hover
+ background-color: bulmaRgba(black, 0.05)
+ // Modifers
+ &.is-active
+ span
+ &:nth-child(1)
+ transform: translateY(5px) rotate(45deg)
+ &:nth-child(2)
+ opacity: 0
+ &:nth-child(3)
+ transform: translateY(-5px) rotate(-45deg)
+
+=overflow-touch
+ -webkit-overflow-scrolling: touch
+
+=placeholder
+ $placeholders: ':-moz' ':-webkit-input' '-moz' '-ms-input'
+ @each $placeholder in $placeholders
+ &:#{$placeholder}-placeholder
+ @content
+
+// Responsiveness
+
+=from($device)
+ @media screen and (min-width: $device)
+ @content
+
+=until($device)
+ @media screen and (max-width: $device - 1px)
+ @content
+
+=mobile
+ @media screen and (max-width: $tablet - 1px)
+ @content
+
+=tablet
+ @media screen and (min-width: $tablet), print
+ @content
+
+=tablet-only
+ @media screen and (min-width: $tablet) and (max-width: $desktop - 1px)
+ @content
+
+=touch
+ @media screen and (max-width: $desktop - 1px)
+ @content
+
+=desktop
+ @media screen and (min-width: $desktop)
+ @content
+
+=desktop-only
+ @if $widescreen-enabled
+ @media screen and (min-width: $desktop) and (max-width: $widescreen - 1px)
+ @content
+
+=until-widescreen
+ @if $widescreen-enabled
+ @media screen and (max-width: $widescreen - 1px)
+ @content
+
+=widescreen
+ @if $widescreen-enabled
+ @media screen and (min-width: $widescreen)
+ @content
+
+=widescreen-only
+ @if $widescreen-enabled and $fullhd-enabled
+ @media screen and (min-width: $widescreen) and (max-width: $fullhd - 1px)
+ @content
+
+=until-fullhd
+ @if $fullhd-enabled
+ @media screen and (max-width: $fullhd - 1px)
+ @content
+
+=fullhd
+ @if $fullhd-enabled
+ @media screen and (min-width: $fullhd)
+ @content
+
+// Placeholders
+
+=unselectable
+ -webkit-touch-callout: none
+ -webkit-user-select: none
+ -moz-user-select: none
+ -ms-user-select: none
+ user-select: none
+
+%unselectable
+ +unselectable
+
+=arrow($color: transparent)
+ border: 3px solid $color
+ border-radius: 2px
+ border-right: 0
+ border-top: 0
+ content: " "
+ display: block
+ height: 0.625em
+ margin-top: -0.4375em
+ pointer-events: none
+ position: absolute
+ top: 50%
+ transform: rotate(-45deg)
+ transform-origin: center
+ width: 0.625em
+
+%arrow
+ +arrow
+
+=block($spacing: $block-spacing)
+ &:not(:last-child)
+ margin-bottom: $spacing
+
+%block
+ +block
+
+=delete
+ @extend %unselectable
+ -moz-appearance: none
+ -webkit-appearance: none
+ background-color: bulmaRgba($scheme-invert, 0.2)
+ border: none
+ border-radius: $radius-rounded
+ cursor: pointer
+ pointer-events: auto
+ display: inline-block
+ flex-grow: 0
+ flex-shrink: 0
+ font-size: 0
+ height: 20px
+ max-height: 20px
+ max-width: 20px
+ min-height: 20px
+ min-width: 20px
+ outline: none
+ position: relative
+ vertical-align: top
+ width: 20px
+ &::before,
+ &::after
+ background-color: $scheme-main
+ content: ""
+ display: block
+ left: 50%
+ position: absolute
+ top: 50%
+ transform: translateX(-50%) translateY(-50%) rotate(45deg)
+ transform-origin: center center
+ &::before
+ height: 2px
+ width: 50%
+ &::after
+ height: 50%
+ width: 2px
+ &:hover,
+ &:focus
+ background-color: bulmaRgba($scheme-invert, 0.3)
+ &:active
+ background-color: bulmaRgba($scheme-invert, 0.4)
+ // Sizes
+ &.is-small
+ height: 16px
+ max-height: 16px
+ max-width: 16px
+ min-height: 16px
+ min-width: 16px
+ width: 16px
+ &.is-medium
+ height: 24px
+ max-height: 24px
+ max-width: 24px
+ min-height: 24px
+ min-width: 24px
+ width: 24px
+ &.is-large
+ height: 32px
+ max-height: 32px
+ max-width: 32px
+ min-height: 32px
+ min-width: 32px
+ width: 32px
+
+%delete
+ +delete
+
+=loader
+ animation: spinAround 500ms infinite linear
+ border: 2px solid $grey-lighter
+ border-radius: $radius-rounded
+ border-right-color: transparent
+ border-top-color: transparent
+ content: ""
+ display: block
+ height: 1em
+ position: relative
+ width: 1em
+
+%loader
+ +loader
+
+=overlay($offset: 0)
+ bottom: $offset
+ left: $offset
+ position: absolute
+ right: $offset
+ top: $offset
+
+%overlay
+ +overlay
diff --git a/docs/style/fa/_animated.scss b/docs/src/assets/fa/_animated.scss
index 7c7c0e17..7c7c0e17 100644
--- a/docs/style/fa/_animated.scss
+++ b/docs/src/assets/fa/_animated.scss
diff --git a/docs/style/fa/_bordered-pulled.scss b/docs/src/assets/fa/_bordered-pulled.scss
index c8c4274c..c8c4274c 100644
--- a/docs/style/fa/_bordered-pulled.scss
+++ b/docs/src/assets/fa/_bordered-pulled.scss
diff --git a/docs/style/fa/_core.scss b/docs/src/assets/fa/_core.scss
index 7fd37f85..7fd37f85 100644
--- a/docs/style/fa/_core.scss
+++ b/docs/src/assets/fa/_core.scss
diff --git a/docs/style/fa/_fixed-width.scss b/docs/src/assets/fa/_fixed-width.scss
index 5b33eb49..5b33eb49 100644
--- a/docs/style/fa/_fixed-width.scss
+++ b/docs/src/assets/fa/_fixed-width.scss
diff --git a/docs/style/fa/_icons.scss b/docs/src/assets/fa/_icons.scss
index bfd2b469..bfd2b469 100644
--- a/docs/style/fa/_icons.scss
+++ b/docs/src/assets/fa/_icons.scss
diff --git a/docs/style/fa/_larger.scss b/docs/src/assets/fa/_larger.scss
index 27c2ad5f..27c2ad5f 100644
--- a/docs/style/fa/_larger.scss
+++ b/docs/src/assets/fa/_larger.scss
diff --git a/docs/style/fa/_list.scss b/docs/src/assets/fa/_list.scss
index 8ebf3333..8ebf3333 100644
--- a/docs/style/fa/_list.scss
+++ b/docs/src/assets/fa/_list.scss
diff --git a/docs/style/fa/_mixins.scss b/docs/src/assets/fa/_mixins.scss
index 06e549b6..06e549b6 100644
--- a/docs/style/fa/_mixins.scss
+++ b/docs/src/assets/fa/_mixins.scss
diff --git a/docs/style/fa/_rotated-flipped.scss b/docs/src/assets/fa/_rotated-flipped.scss
index 995bc4cc..995bc4cc 100644
--- a/docs/style/fa/_rotated-flipped.scss
+++ b/docs/src/assets/fa/_rotated-flipped.scss
diff --git a/docs/style/fa/_screen-reader.scss b/docs/src/assets/fa/_screen-reader.scss
index 5d0ab262..5d0ab262 100644
--- a/docs/style/fa/_screen-reader.scss
+++ b/docs/src/assets/fa/_screen-reader.scss
diff --git a/docs/style/fa/_stacked.scss b/docs/src/assets/fa/_stacked.scss
index 6c09d84c..6c09d84c 100644
--- a/docs/style/fa/_stacked.scss
+++ b/docs/src/assets/fa/_stacked.scss
diff --git a/docs/style/fa/_variables.scss b/docs/src/assets/fa/_variables.scss
index d2c4d474..d2c4d474 100644
--- a/docs/style/fa/_variables.scss
+++ b/docs/src/assets/fa/_variables.scss
diff --git a/docs/style/fa/fa-brands.scss b/docs/src/assets/fa/fa-brands.scss
index ab89c020..ab89c020 100644
--- a/docs/style/fa/fa-brands.scss
+++ b/docs/src/assets/fa/fa-brands.scss
diff --git a/docs/style/fa/fa-regular.scss b/docs/src/assets/fa/fa-regular.scss
index 4f288292..4f288292 100644
--- a/docs/style/fa/fa-regular.scss
+++ b/docs/src/assets/fa/fa-regular.scss
diff --git a/docs/style/fa/fa-solid.scss b/docs/src/assets/fa/fa-solid.scss
index bb0df52a..bb0df52a 100644
--- a/docs/style/fa/fa-solid.scss
+++ b/docs/src/assets/fa/fa-solid.scss
diff --git a/docs/style/fa/fontawesome.scss b/docs/src/assets/fa/fontawesome.scss
index 04eb879a..04eb879a 100644
--- a/docs/style/fa/fontawesome.scss
+++ b/docs/src/assets/fa/fontawesome.scss
diff --git a/docs/style/style.scss b/docs/src/assets/style.scss
index 908950d7..26c22071 100644
--- a/docs/style/style.scss
+++ b/docs/src/assets/style.scss
@@ -4,12 +4,15 @@
$primary: #C93312;
$warning-invert: #FFFFFF;
$family-sans-serif: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif, 'Font Awesome 5 Free', 'Font Awesome 5 Brands' !default;
-@import "../node_modules/bulma/sass/utilities/_all";
-@import "../node_modules/bulma/sass/base/_all";
-@import "../node_modules/bulma/sass/grid/_all";
-@import "../node_modules/bulma/sass/elements/_all";
-@import "../node_modules/bulma/sass/components/_all";
-@import "../node_modules/bulma/sass/layout/_all";
+
+/*!*
+bulma.io v0.8.0 | MIT License | github.com/jgthms/bulma */
+@import "./bulma/utilities/_all";
+@import "./bulma/base/_all";
+@import "./bulma/grid/_all";
+@import "./bulma/elements/_all";
+@import "./bulma/components/_all";
+@import "./bulma/layout/_all";
html, body {
height: 100%;
diff --git a/docs/style/syntax.css b/docs/src/assets/syntax.css
index 33ce367c..33ce367c 100644
--- a/docs/style/syntax.css
+++ b/docs/src/assets/syntax.css
diff --git a/docs/src/content/addons-events.md b/docs/src/content/addons-events.md
index 489ea342..ddf0a99f 100644
--- a/docs/src/content/addons-events.md
+++ b/docs/src/content/addons-events.md
@@ -21,4 +21,18 @@ header with a count of the number of responses seen:
Below is an addon class that implements stubs for all events. We've added
annotations to illustrate the argument types for the various events.
-{{< example src="examples/addons/events.py" lang="py" >}} \ No newline at end of file
+### Generic Events
+
+{{< example src="examples/addons/events.py" lang="py" >}}
+
+### HTTP Events
+
+{{< example src="examples/addons/events-http-specific.py" lang="py" >}}
+
+### WebSocket Events
+
+{{< example src="examples/addons/events-websocket-specific.py" lang="py" >}}
+
+### TCP Events
+
+{{< example src="examples/addons/events-tcp-specific.py" lang="py" >}}
diff --git a/docs/src/content/addons-scripting.md b/docs/src/content/addons-scripting.md
index 6a18eaf4..c90a9037 100644
--- a/docs/src/content/addons-scripting.md
+++ b/docs/src/content/addons-scripting.md
@@ -5,7 +5,7 @@ menu:
weight: 5
---
-# Scripting
+# Scripting HTTP/1.1 and HTTP/2.0
Sometimes, we would like to write a quick script without going through the
trouble of creating a class. The addons mechanism has a shorthand that allows a
@@ -13,7 +13,6 @@ module as a whole to be treated as an addon object. This lets us place event
handler functions in the module scope. For instance, here is a complete script
that adds a header to every request.
-
{{< example src="examples/addons/scripting-headers.py" lang="py" >}}
@@ -22,11 +21,31 @@ an arbitrary response instead:
{{< example src="examples/simple/send_reply_from_proxy.py" lang="py" >}}
+All events around the HTTP protocol [can be found here]({{< relref "addons-events#http-events">}}).
-You can look at the [http][] module, or the [Request][], and
-[Response][] classes for other attributes that you can use when
+For HTTP-related objects, please look at the [http][] module, or the
+[Request][], and [Response][] classes for other attributes that you can use when
scripting.
-[http]: https://github.com/mitmproxy/mitmproxy/blob/master/mitmproxy/http.py
-[Request]: https://github.com/mitmproxy/mitmproxy/blob/master/mitmproxy/net/http/request.py
-[Response]: https://github.com/mitmproxy/mitmproxy/blob/master/mitmproxy/net/http/response.py
+# Scripting WebSocket
+
+The WebSocket protocol initially looks like a regular HTTP request, before the client and server agree to upgrade the connection to WebSocket. All scripting events for initial HTTP handshake, and also the dedicated WebSocket events [can be found here]({{< relref "addons-events#websocket-events">}}).
+
+{{< example src="examples/simple/websocket_messages.py" lang="py" >}}
+
+For WebSocket-related objects please look at the [websocket][] module to find
+all attributes that you can use when scripting.
+
+[websocket]: https://github.com/mitmproxy/mitmproxy/blob/master/mitmproxy/websocket.py
+
+
+# Scripting TCP
+
+All events around the TCP protocol [can be found here]({{< relref "addons-events#tcp-events">}}).
+
+{{< example src="examples/complex/tcp_message.py" lang="py" >}}
+
+For WebSocket-related objects please look at the [tcp][] module to find
+all attributes that you can use when scripting.
+
+[tcp]: https://github.com/mitmproxy/mitmproxy/blob/master/mitmproxy/tcp.py
diff --git a/docs/src/content/concepts-certificates.md b/docs/src/content/concepts-certificates.md
index 4e2ae47a..4e9aa652 100644
--- a/docs/src/content/concepts-certificates.md
+++ b/docs/src/content/concepts-certificates.md
@@ -48,7 +48,7 @@ documentation for some common platforms. The mitmproxy CA cert is located in
- [Windows (automated)](https://technet.microsoft.com/en-us/library/cc732443.aspx)
{{< highlight bash >}}
-certutil.exe -importpfx Root mitmproxy-ca-cert.p12
+certutil -addstore root mitmproxy-ca-cert.cer
{{< / highlight >}}
- [Mac OS X](https://support.apple.com/kb/PH20129)
diff --git a/docs/src/content/concepts-commands.md b/docs/src/content/concepts-commands.md
index 72f17252..46d76209 100644
--- a/docs/src/content/concepts-commands.md
+++ b/docs/src/content/concepts-commands.md
@@ -60,13 +60,3 @@ just replay flows for a specific domain:
{{< highlight none >}}
:replay.client "~d google.com"
{{< /highlight >}}
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/src/content/concepts-filters.md b/docs/src/content/concepts-filters.md
index f0b9a4b2..b9c89456 100644
--- a/docs/src/content/concepts-filters.md
+++ b/docs/src/content/concepts-filters.md
@@ -49,3 +49,8 @@ Requests whose body contains the string "test":
Anything but requests with a text/html content type:
!(~q & ~t "text/html")
+
+Replace entire GET string in a request (quotes required to make it work):
+
+ ":~q ~m GET:.*:/replacement.html"
+
diff --git a/docs/src/content/concepts-protocols.md b/docs/src/content/concepts-protocols.md
index c79274bf..dc9d84fb 100644
--- a/docs/src/content/concepts-protocols.md
+++ b/docs/src/content/concepts-protocols.md
@@ -73,6 +73,8 @@ If an endpoint sends a PING to mitmproxy, a PONG will be sent back immediately
PING (without a payload) is sent to the other endpoint. Unsolicited PONG's are
not forwarded. All PING's and PONG's are logged (with payload if present).
+Please note that message interception, modification or replay are not possible yet.
+
## Raw TCP / TCP Proxy / Fallback
In case mitmproxy does not handle a specific protocol, you can exempt
@@ -83,7 +85,7 @@ but differs in two important aspects:
* The raw TCP messages are printed to the event log.
* SSL connections will be intercepted.
-Please note that message interception or modification are not possible yet. If
+Please note that message interception, modification or replay are not possible yet. If
you are not interested in the raw TCP messages, you should use the ignore
domains feature.
diff --git a/docs/src/content/tools-mitmdump.md b/docs/src/content/tools-mitmdump.md
index fb6a6c0c..624246d4 100644
--- a/docs/src/content/tools-mitmdump.md
+++ b/docs/src/content/tools-mitmdump.md
@@ -35,16 +35,16 @@ and write to outfile.
### Client replay
{{< highlight bash >}}
-mitmdump -nc outfile
+mitmdump -nC outfile
{{< / highlight >}}
Start mitmdump without binding to the proxy port (`-n`), then replay all
-requests from outfile (`-c filename`). Flags combine in the obvious way,
+requests from outfile (`-C filename`). Flags combine in the obvious way,
so you can replay requests from one file, and write the resulting flows
to another:
{{< highlight bash >}}
-mitmdump -nc srcfile -w dstfile
+mitmdump -nC srcfile -w dstfile
{{< / highlight >}}
See the [client-side replay]({{< relref "overview-features#client-side-replay"
@@ -53,7 +53,7 @@ See the [client-side replay]({{< relref "overview-features#client-side-replay"
### Running a script
{{< highlight bash >}}
-mitmdump -s examples/add_header.py
+mitmdump -s examples/simple/add_header.py
{{< / highlight >}}
This runs the **add_header.py** example script, which simply adds a new
@@ -62,7 +62,7 @@ header to all responses.
### Scripted data transformation
{{< highlight bash >}}
-mitmdump -ns examples/add_header.py -r srcfile -w dstfile
+mitmdump -ns examples/simple/add_header.py -r srcfile -w dstfile
{{< / highlight >}}
This command loads flows from **srcfile**, transforms it according to
diff --git a/docs/src/content/tute-clientreplay.md b/docs/src/content/tute-clientreplay.md
index 1bf69031..048fd0a5 100644
--- a/docs/src/content/tute-clientreplay.md
+++ b/docs/src/content/tute-clientreplay.md
@@ -31,9 +31,8 @@ mitmdump -w wireless-login
## 2. Point your browser at the mitmdump instance.
-I use a tiny Firefox addon called [Toggle
-Proxy](https://addons.mozilla.org/en-us/firefox/addon/toggle-proxy-51740/) to
-switch quickly to and from mitmproxy. I'm assuming you've already [configured
+There is a Firefox addon called [FoxyProxy](https://addons.mozilla.org/fi/firefox/addon/foxyproxy-standard/) that
+lets you switch quickly to and from mitmproxy. I'm assuming you've already [configured
your browser with mitmproxy's SSL certificate authority]({{< relref
"concepts-certificates" >}}).
@@ -43,7 +42,7 @@ And that's it\! You now have a serialised version of the login process
in the file wireless-login, and you can replay it at any time like this:
{{< highlight bash >}}
-mitmdump -c wireless-login
+mitmdump -C wireless-login
{{< / highlight >}}
## Embellishments
diff --git a/docs/src/themes/mitmproxydocs/layouts/partials/header.html b/docs/src/themes/mitmproxydocs/layouts/partials/header.html
index 559115f0..925631d6 100644
--- a/docs/src/themes/mitmproxydocs/layouts/partials/header.html
+++ b/docs/src/themes/mitmproxydocs/layouts/partials/header.html
@@ -13,10 +13,13 @@
{{ with .Site.Params.author }}
<meta name="author" content="{{ . }}">
{{ end }}
- <link rel="stylesheet" href="/css/style.css">
+
+ {{ $style := resources.Get "style.scss" | toCSS | minify }}
+ <link rel="stylesheet" href="{{ $style.Permalink }}">
+
{{ if .RSSLink -}}
<link href="{{ .RSSLink }}" rel="feed" type="application/rss+xml" title="{{ .Site.Title }}">
{{- end }}
- {{ .Hugo.Generator }}
+ {{ hugo.Generator }}
</head>
<body>
diff --git a/docs/yarn.lock b/docs/yarn.lock
deleted file mode 100644
index 5f5b2ee6..00000000
--- a/docs/yarn.lock
+++ /dev/null
@@ -1,1203 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-abbrev@1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
-
-ajv@^5.1.0:
- version "5.5.1"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.1.tgz#b38bb8876d9e86bee994956a04e721e88b248eb2"
- dependencies:
- co "^4.6.0"
- fast-deep-equal "^1.0.0"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.3.0"
-
-amdefine@>=0.0.4:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
-
-ansi-regex@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
-
-ansi-styles@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
-
-aproba@^1.0.3:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
-
-are-we-there-yet@~1.1.2:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
- dependencies:
- delegates "^1.0.0"
- readable-stream "^2.0.6"
-
-array-find-index@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
-
-asn1@~0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
-
-assert-plus@1.0.0, assert-plus@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
-
-assert-plus@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
-
-async-foreach@^0.1.3:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
-
-asynckit@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
-
-aws-sign2@~0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
-
-aws-sign2@~0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
-
-aws4@^1.2.1, aws4@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
-
-balanced-match@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
-
-bcrypt-pbkdf@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
- dependencies:
- tweetnacl "^0.14.3"
-
-block-stream@*:
- version "0.0.9"
- resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
- dependencies:
- inherits "~2.0.0"
-
-boom@2.x.x:
- version "2.10.1"
- resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
- dependencies:
- hoek "2.x.x"
-
-boom@4.x.x:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
- dependencies:
- hoek "4.x.x"
-
-boom@5.x.x:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
- dependencies:
- hoek "4.x.x"
-
-brace-expansion@^1.1.7:
- version "1.1.8"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
- dependencies:
- balanced-match "^1.0.0"
- concat-map "0.0.1"
-
-builtin-modules@^1.0.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
-
-bulma@^0.6.1:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.6.1.tgz#5f21a77c0c06f7d80051c06628c23516081bd649"
-
-camelcase-keys@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
- dependencies:
- camelcase "^2.0.0"
- map-obj "^1.0.0"
-
-camelcase@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
-
-camelcase@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
-
-caseless@~0.11.0:
- version "0.11.0"
- resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
-
-caseless@~0.12.0:
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
-
-chalk@^1.1.1:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
- dependencies:
- ansi-styles "^2.2.1"
- escape-string-regexp "^1.0.2"
- has-ansi "^2.0.0"
- strip-ansi "^3.0.0"
- supports-color "^2.0.0"
-
-cliui@^3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
- dependencies:
- string-width "^1.0.1"
- strip-ansi "^3.0.1"
- wrap-ansi "^2.0.0"
-
-co@^4.6.0:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
-
-code-point-at@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
-
-combined-stream@^1.0.5, combined-stream@~1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
- dependencies:
- delayed-stream "~1.0.0"
-
-commander@^2.9.0:
- version "2.12.2"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
-
-concat-map@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-
-console-control-strings@^1.0.0, console-control-strings@~1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
-
-core-util-is@1.0.2, core-util-is@~1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
-
-cross-spawn@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
- dependencies:
- lru-cache "^4.0.1"
- which "^1.2.9"
-
-cryptiles@2.x.x:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
- dependencies:
- boom "2.x.x"
-
-cryptiles@3.x.x:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
- dependencies:
- boom "5.x.x"
-
-currently-unhandled@^0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
- dependencies:
- array-find-index "^1.0.1"
-
-dashdash@^1.12.0:
- version "1.14.1"
- resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
- dependencies:
- assert-plus "^1.0.0"
-
-decamelize@^1.1.1, decamelize@^1.1.2:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
-
-delayed-stream@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
-
-delegates@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
-
-ecc-jsbn@~0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
- dependencies:
- jsbn "~0.1.0"
-
-error-ex@^1.2.0:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
- dependencies:
- is-arrayish "^0.2.1"
-
-escape-string-regexp@^1.0.2:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
-
-extend@~3.0.0, extend@~3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
-
-extsprintf@1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
-
-extsprintf@^1.2.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
-
-fast-deep-equal@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
-
-fast-json-stable-stringify@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
-
-find-up@^1.0.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
- dependencies:
- path-exists "^2.0.0"
- pinkie-promise "^2.0.0"
-
-forever-agent@~0.6.1:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
-
-form-data@~2.1.1:
- version "2.1.4"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.5"
- mime-types "^2.1.12"
-
-form-data@~2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.5"
- mime-types "^2.1.12"
-
-fs.realpath@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-
-fstream@^1.0.0, fstream@^1.0.2:
- version "1.0.11"
- resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
- dependencies:
- graceful-fs "^4.1.2"
- inherits "~2.0.0"
- mkdirp ">=0.5 0"
- rimraf "2"
-
-gauge@~2.7.3:
- version "2.7.4"
- resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
- dependencies:
- aproba "^1.0.3"
- console-control-strings "^1.0.0"
- has-unicode "^2.0.0"
- object-assign "^4.1.0"
- signal-exit "^3.0.0"
- string-width "^1.0.1"
- strip-ansi "^3.0.1"
- wide-align "^1.1.0"
-
-gaze@^1.0.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.2.tgz#847224677adb8870d679257ed3388fdb61e40105"
- dependencies:
- globule "^1.0.0"
-
-generate-function@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
-
-generate-object-property@^1.1.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
- dependencies:
- is-property "^1.0.0"
-
-get-caller-file@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
-
-get-stdin@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
-
-getpass@^0.1.1:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
- dependencies:
- assert-plus "^1.0.0"
-
-glob@^6.0.4:
- version "6.0.4"
- resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
- dependencies:
- inflight "^1.0.4"
- inherits "2"
- minimatch "2 || 3"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
-glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@~7.1.1:
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.0.4"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
-globule@^1.0.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09"
- dependencies:
- glob "~7.1.1"
- lodash "~4.17.4"
- minimatch "~3.0.2"
-
-graceful-fs@^4.1.2:
- version "4.1.11"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
-
-har-schema@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
-
-har-validator@~2.0.6:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
- dependencies:
- chalk "^1.1.1"
- commander "^2.9.0"
- is-my-json-valid "^2.12.4"
- pinkie-promise "^2.0.0"
-
-har-validator@~5.0.3:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
- dependencies:
- ajv "^5.1.0"
- har-schema "^2.0.0"
-
-has-ansi@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
- dependencies:
- ansi-regex "^2.0.0"
-
-has-unicode@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
-
-hawk@~3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
- dependencies:
- boom "2.x.x"
- cryptiles "2.x.x"
- hoek "2.x.x"
- sntp "1.x.x"
-
-hawk@~6.0.2:
- version "6.0.2"
- resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
- dependencies:
- boom "4.x.x"
- cryptiles "3.x.x"
- hoek "4.x.x"
- sntp "2.x.x"
-
-hoek@2.x.x:
- version "2.16.3"
- resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
-
-hoek@4.x.x:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
-
-hosted-git-info@^2.1.4:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
-
-http-signature@~1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
- dependencies:
- assert-plus "^0.2.0"
- jsprim "^1.2.2"
- sshpk "^1.7.0"
-
-http-signature@~1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
- dependencies:
- assert-plus "^1.0.0"
- jsprim "^1.2.2"
- sshpk "^1.7.0"
-
-in-publish@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51"
-
-indent-string@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
- dependencies:
- repeating "^2.0.0"
-
-inflight@^1.0.4:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
- dependencies:
- once "^1.3.0"
- wrappy "1"
-
-inherits@2, inherits@~2.0.0, inherits@~2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
-
-invert-kv@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
-
-is-arrayish@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
-
-is-builtin-module@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
- dependencies:
- builtin-modules "^1.0.0"
-
-is-finite@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
- dependencies:
- number-is-nan "^1.0.0"
-
-is-fullwidth-code-point@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
- dependencies:
- number-is-nan "^1.0.0"
-
-is-my-json-valid@^2.12.4:
- version "2.16.1"
- resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11"
- dependencies:
- generate-function "^2.0.0"
- generate-object-property "^1.1.0"
- jsonpointer "^4.0.0"
- xtend "^4.0.0"
-
-is-property@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
-
-is-typedarray@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
-
-is-utf8@^0.2.0:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
-
-isarray@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
-
-isexe@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
-
-isstream@~0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
-
-js-base64@^2.1.8:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.0.tgz#9e566fee624751a1d720c966cd6226d29d4025aa"
-
-jsbn@~0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
-
-json-schema-traverse@^0.3.0:
- version "0.3.1"
- resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
-
-json-schema@0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
-
-json-stringify-safe@~5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
-
-jsonpointer@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
-
-jsprim@^1.2.2:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
- dependencies:
- assert-plus "1.0.0"
- extsprintf "1.3.0"
- json-schema "0.2.3"
- verror "1.10.0"
-
-lcid@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
- dependencies:
- invert-kv "^1.0.0"
-
-load-json-file@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
- dependencies:
- graceful-fs "^4.1.2"
- parse-json "^2.2.0"
- pify "^2.0.0"
- pinkie-promise "^2.0.0"
- strip-bom "^2.0.0"
-
-lodash.assign@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
-
-lodash.clonedeep@^4.3.2:
- version "4.5.0"
- resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
-
-lodash.mergewith@^4.6.0:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55"
-
-lodash@^4.0.0, lodash@~4.17.4:
- version "4.17.4"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
-
-loud-rejection@^1.0.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
- dependencies:
- currently-unhandled "^0.4.1"
- signal-exit "^3.0.0"
-
-lru-cache@^4.0.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
- dependencies:
- pseudomap "^1.0.2"
- yallist "^2.1.2"
-
-map-obj@^1.0.0, map-obj@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
-
-meow@^3.7.0:
- version "3.7.0"
- resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
- dependencies:
- camelcase-keys "^2.0.0"
- decamelize "^1.1.2"
- loud-rejection "^1.0.0"
- map-obj "^1.0.1"
- minimist "^1.1.3"
- normalize-package-data "^2.3.4"
- object-assign "^4.0.1"
- read-pkg-up "^1.0.1"
- redent "^1.0.0"
- trim-newlines "^1.0.0"
-
-mime-db@~1.30.0:
- version "1.30.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
-
-mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7:
- version "2.1.17"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
- dependencies:
- mime-db "~1.30.0"
-
-"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
- dependencies:
- brace-expansion "^1.1.7"
-
-minimist@0.0.8:
- version "0.0.8"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-
-minimist@^1.1.3:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
-
-"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
- dependencies:
- minimist "0.0.8"
-
-nan@^2.3.2:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
-
-node-gyp@^3.3.1:
- version "3.6.2"
- resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60"
- dependencies:
- fstream "^1.0.0"
- glob "^7.0.3"
- graceful-fs "^4.1.2"
- minimatch "^3.0.2"
- mkdirp "^0.5.0"
- nopt "2 || 3"
- npmlog "0 || 1 || 2 || 3 || 4"
- osenv "0"
- request "2"
- rimraf "2"
- semver "~5.3.0"
- tar "^2.0.0"
- which "1"
-
-node-sass@^4.7.2:
- version "4.7.2"
- resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e"
- dependencies:
- async-foreach "^0.1.3"
- chalk "^1.1.1"
- cross-spawn "^3.0.0"
- gaze "^1.0.0"
- get-stdin "^4.0.1"
- glob "^7.0.3"
- in-publish "^2.0.0"
- lodash.assign "^4.2.0"
- lodash.clonedeep "^4.3.2"
- lodash.mergewith "^4.6.0"
- meow "^3.7.0"
- mkdirp "^0.5.1"
- nan "^2.3.2"
- node-gyp "^3.3.1"
- npmlog "^4.0.0"
- request "~2.79.0"
- sass-graph "^2.2.4"
- stdout-stream "^1.4.0"
- "true-case-path" "^1.0.2"
-
-"nopt@2 || 3":
- version "3.0.6"
- resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
- dependencies:
- abbrev "1"
-
-normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
- dependencies:
- hosted-git-info "^2.1.4"
- is-builtin-module "^1.0.0"
- semver "2 || 3 || 4 || 5"
- validate-npm-package-license "^3.0.1"
-
-"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
- dependencies:
- are-we-there-yet "~1.1.2"
- console-control-strings "~1.1.0"
- gauge "~2.7.3"
- set-blocking "~2.0.0"
-
-number-is-nan@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
-
-oauth-sign@~0.8.1, oauth-sign@~0.8.2:
- version "0.8.2"
- resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
-
-object-assign@^4.0.1, object-assign@^4.1.0:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
-
-once@^1.3.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
- dependencies:
- wrappy "1"
-
-os-homedir@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
-
-os-locale@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
- dependencies:
- lcid "^1.0.0"
-
-os-tmpdir@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
-
-osenv@0:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
- dependencies:
- os-homedir "^1.0.0"
- os-tmpdir "^1.0.0"
-
-parse-json@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
- dependencies:
- error-ex "^1.2.0"
-
-path-exists@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
- dependencies:
- pinkie-promise "^2.0.0"
-
-path-is-absolute@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
-
-path-type@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
- dependencies:
- graceful-fs "^4.1.2"
- pify "^2.0.0"
- pinkie-promise "^2.0.0"
-
-performance-now@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
-
-pify@^2.0.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
-
-pinkie-promise@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
- dependencies:
- pinkie "^2.0.0"
-
-pinkie@^2.0.0:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
-
-process-nextick-args@~1.0.6:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
-
-pseudomap@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
-
-punycode@^1.4.1:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
-
-qs@~6.3.0:
- version "6.3.2"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
-
-qs@~6.5.1:
- version "6.5.1"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
-
-read-pkg-up@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
- dependencies:
- find-up "^1.0.0"
- read-pkg "^1.0.0"
-
-read-pkg@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
- dependencies:
- load-json-file "^1.0.0"
- normalize-package-data "^2.3.2"
- path-type "^1.0.0"
-
-readable-stream@^2.0.1, readable-stream@^2.0.6:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.3"
- isarray "~1.0.0"
- process-nextick-args "~1.0.6"
- safe-buffer "~5.1.1"
- string_decoder "~1.0.3"
- util-deprecate "~1.0.1"
-
-redent@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
- dependencies:
- indent-string "^2.1.0"
- strip-indent "^1.0.1"
-
-repeating@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
- dependencies:
- is-finite "^1.0.0"
-
-request@2:
- version "2.83.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
- dependencies:
- aws-sign2 "~0.7.0"
- aws4 "^1.6.0"
- caseless "~0.12.0"
- combined-stream "~1.0.5"
- extend "~3.0.1"
- forever-agent "~0.6.1"
- form-data "~2.3.1"
- har-validator "~5.0.3"
- hawk "~6.0.2"
- http-signature "~1.2.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.17"
- oauth-sign "~0.8.2"
- performance-now "^2.1.0"
- qs "~6.5.1"
- safe-buffer "^5.1.1"
- stringstream "~0.0.5"
- tough-cookie "~2.3.3"
- tunnel-agent "^0.6.0"
- uuid "^3.1.0"
-
-request@~2.79.0:
- version "2.79.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
- dependencies:
- aws-sign2 "~0.6.0"
- aws4 "^1.2.1"
- caseless "~0.11.0"
- combined-stream "~1.0.5"
- extend "~3.0.0"
- forever-agent "~0.6.1"
- form-data "~2.1.1"
- har-validator "~2.0.6"
- hawk "~3.1.3"
- http-signature "~1.1.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.7"
- oauth-sign "~0.8.1"
- qs "~6.3.0"
- stringstream "~0.0.4"
- tough-cookie "~2.3.0"
- tunnel-agent "~0.4.1"
- uuid "^3.0.0"
-
-require-directory@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
-
-require-main-filename@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
-
-rimraf@2:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
- dependencies:
- glob "^7.0.5"
-
-safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
-
-sass-graph@^2.2.4:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
- dependencies:
- glob "^7.0.0"
- lodash "^4.0.0"
- scss-tokenizer "^0.2.3"
- yargs "^7.0.0"
-
-scss-tokenizer@^0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
- dependencies:
- js-base64 "^2.1.8"
- source-map "^0.4.2"
-
-"semver@2 || 3 || 4 || 5":
- version "5.4.1"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
-
-semver@~5.3.0:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
-
-set-blocking@^2.0.0, set-blocking@~2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
-
-signal-exit@^3.0.0:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
-
-sntp@1.x.x:
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
- dependencies:
- hoek "2.x.x"
-
-sntp@2.x.x:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
- dependencies:
- hoek "4.x.x"
-
-source-map@^0.4.2:
- version "0.4.4"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
- dependencies:
- amdefine ">=0.0.4"
-
-spdx-correct@~1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
- dependencies:
- spdx-license-ids "^1.0.2"
-
-spdx-expression-parse@~1.0.0:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
-
-spdx-license-ids@^1.0.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
-
-sshpk@^1.7.0:
- version "1.13.1"
- resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
- dependencies:
- asn1 "~0.2.3"
- assert-plus "^1.0.0"
- dashdash "^1.12.0"
- getpass "^0.1.1"
- optionalDependencies:
- bcrypt-pbkdf "^1.0.0"
- ecc-jsbn "~0.1.1"
- jsbn "~0.1.0"
- tweetnacl "~0.14.0"
-
-stdout-stream@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b"
- dependencies:
- readable-stream "^2.0.1"
-
-string-width@^1.0.1, string-width@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
- dependencies:
- code-point-at "^1.0.0"
- is-fullwidth-code-point "^1.0.0"
- strip-ansi "^3.0.0"
-
-string_decoder@~1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
- dependencies:
- safe-buffer "~5.1.0"
-
-stringstream@~0.0.4, stringstream@~0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
-
-strip-ansi@^3.0.0, strip-ansi@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
- dependencies:
- ansi-regex "^2.0.0"
-
-strip-bom@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
- dependencies:
- is-utf8 "^0.2.0"
-
-strip-indent@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
- dependencies:
- get-stdin "^4.0.1"
-
-supports-color@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-
-tar@^2.0.0:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
- dependencies:
- block-stream "*"
- fstream "^1.0.2"
- inherits "2"
-
-tough-cookie@~2.3.0, tough-cookie@~2.3.3:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
- dependencies:
- punycode "^1.4.1"
-
-trim-newlines@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
-
-"true-case-path@^1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62"
- dependencies:
- glob "^6.0.4"
-
-tunnel-agent@^0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
- dependencies:
- safe-buffer "^5.0.1"
-
-tunnel-agent@~0.4.1:
- version "0.4.3"
- resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
-
-tweetnacl@^0.14.3, tweetnacl@~0.14.0:
- version "0.14.5"
- resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
-
-util-deprecate@~1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
-
-uuid@^3.0.0, uuid@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
-
-validate-npm-package-license@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
- dependencies:
- spdx-correct "~1.0.0"
- spdx-expression-parse "~1.0.0"
-
-verror@1.10.0:
- version "1.10.0"
- resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
- dependencies:
- assert-plus "^1.0.0"
- core-util-is "1.0.2"
- extsprintf "^1.2.0"
-
-which-module@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
-
-which@1, which@^1.2.9:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
- dependencies:
- isexe "^2.0.0"
-
-wide-align@^1.1.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
- dependencies:
- string-width "^1.0.2"
-
-wrap-ansi@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
- dependencies:
- string-width "^1.0.1"
- strip-ansi "^3.0.1"
-
-wrappy@1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
-
-xtend@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
-
-y18n@^3.2.1:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
-
-yallist@^2.1.2:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
-
-yargs-parser@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
- dependencies:
- camelcase "^3.0.0"
-
-yargs@^7.0.0:
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
- dependencies:
- camelcase "^3.0.0"
- cliui "^3.2.0"
- decamelize "^1.1.1"
- get-caller-file "^1.0.1"
- os-locale "^1.4.0"
- read-pkg-up "^1.0.1"
- require-directory "^2.1.1"
- require-main-filename "^1.0.1"
- set-blocking "^2.0.0"
- string-width "^1.0.2"
- which-module "^1.0.0"
- y18n "^3.2.1"
- yargs-parser "^5.0.0"
diff --git a/examples/addons/events-http-specific.py b/examples/addons/events-http-specific.py
new file mode 100644
index 00000000..37d9f91a
--- /dev/null
+++ b/examples/addons/events-http-specific.py
@@ -0,0 +1,42 @@
+import mitmproxy.http
+
+
+class Events:
+ # HTTP lifecycle
+ def http_connect(self, flow: mitmproxy.http.HTTPFlow):
+ """
+ An HTTP CONNECT request was received. Setting a non 2xx response on
+ the flow will return the response to the client abort the
+ connection. CONNECT requests and responses do not generate the usual
+ HTTP handler events. CONNECT requests are only valid in regular and
+ upstream proxy modes.
+ """
+
+ def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
+ """
+ HTTP request headers were successfully read. At this point, the body
+ is empty.
+ """
+
+ def request(self, flow: mitmproxy.http.HTTPFlow):
+ """
+ The full HTTP request has been read.
+ """
+
+ def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
+ """
+ HTTP response headers were successfully read. At this point, the body
+ is empty.
+ """
+
+ def response(self, flow: mitmproxy.http.HTTPFlow):
+ """
+ The full HTTP response has been read.
+ """
+
+ def error(self, flow: mitmproxy.http.HTTPFlow):
+ """
+ An HTTP error has occurred, e.g. invalid server responses, or
+ interrupted connections. This is distinct from a valid server HTTP
+ error response, which is simply a response with an HTTP error code.
+ """
diff --git a/examples/addons/events-tcp-specific.py b/examples/addons/events-tcp-specific.py
new file mode 100644
index 00000000..d150d0f9
--- /dev/null
+++ b/examples/addons/events-tcp-specific.py
@@ -0,0 +1,25 @@
+import mitmproxy.tcp
+
+
+class Events:
+ # TCP lifecycle
+ def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
+ """
+ A TCP connection has started.
+ """
+
+ def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):
+ """
+ A TCP connection has received a message. The most recent message
+ will be flow.messages[-1]. The message is user-modifiable.
+ """
+
+ def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
+ """
+ A TCP error has occurred.
+ """
+
+ def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
+ """
+ A TCP connection has ended.
+ """
diff --git a/examples/addons/events-websocket-specific.py b/examples/addons/events-websocket-specific.py
new file mode 100644
index 00000000..60069fdb
--- /dev/null
+++ b/examples/addons/events-websocket-specific.py
@@ -0,0 +1,36 @@
+import mitmproxy.http
+import mitmproxy.websocket
+
+
+class Events:
+ # Websocket lifecycle
+ def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):
+ """
+ Called when a client wants to establish a WebSocket connection. The
+ WebSocket-specific headers can be manipulated to alter the
+ handshake. The flow object is guaranteed to have a non-None request
+ attribute.
+ """
+
+ def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):
+ """
+ A websocket connection has commenced.
+ """
+
+ def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
+ """
+ Called when a WebSocket message is received from the client or
+ server. The most recent message will be flow.messages[-1]. The
+ message is user-modifiable. Currently there are two types of
+ messages, corresponding to the BINARY and TEXT frame types.
+ """
+
+ def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):
+ """
+ A websocket connection has had an error.
+ """
+
+ def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):
+ """
+ A websocket connection has ended.
+ """
diff --git a/examples/addons/events.py b/examples/addons/events.py
index f83c8f11..958e7d39 100644
--- a/examples/addons/events.py
+++ b/examples/addons/events.py
@@ -10,99 +10,6 @@ import mitmproxy.proxy.protocol
class Events:
- # HTTP lifecycle
- def http_connect(self, flow: mitmproxy.http.HTTPFlow):
- """
- An HTTP CONNECT request was received. Setting a non 2xx response on
- the flow will return the response to the client abort the
- connection. CONNECT requests and responses do not generate the usual
- HTTP handler events. CONNECT requests are only valid in regular and
- upstream proxy modes.
- """
-
- def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
- """
- HTTP request headers were successfully read. At this point, the body
- is empty.
- """
-
- def request(self, flow: mitmproxy.http.HTTPFlow):
- """
- The full HTTP request has been read.
- """
-
- def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
- """
- HTTP response headers were successfully read. At this point, the body
- is empty.
- """
-
- def response(self, flow: mitmproxy.http.HTTPFlow):
- """
- The full HTTP response has been read.
- """
-
- def error(self, flow: mitmproxy.http.HTTPFlow):
- """
- An HTTP error has occurred, e.g. invalid server responses, or
- interrupted connections. This is distinct from a valid server HTTP
- error response, which is simply a response with an HTTP error code.
- """
-
- # TCP lifecycle
- def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
- """
- A TCP connection has started.
- """
-
- def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):
- """
- A TCP connection has received a message. The most recent message
- will be flow.messages[-1]. The message is user-modifiable.
- """
-
- def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
- """
- A TCP error has occurred.
- """
-
- def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
- """
- A TCP connection has ended.
- """
-
- # Websocket lifecycle
- def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):
- """
- Called when a client wants to establish a WebSocket connection. The
- WebSocket-specific headers can be manipulated to alter the
- handshake. The flow object is guaranteed to have a non-None request
- attribute.
- """
-
- def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):
- """
- A websocket connection has commenced.
- """
-
- def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
- """
- Called when a WebSocket message is received from the client or
- server. The most recent message will be flow.messages[-1]. The
- message is user-modifiable. Currently there are two types of
- messages, corresponding to the BINARY and TEXT frame types.
- """
-
- def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):
- """
- A websocket connection has had an error.
- """
-
- def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):
- """
- A websocket connection has ended.
- """
-
# Network lifecycle
def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):
"""
diff --git a/examples/complex/README.md b/examples/complex/README.md
index c53503e4..923aadf1 100644
--- a/examples/complex/README.md
+++ b/examples/complex/README.md
@@ -2,6 +2,7 @@
| Filename | Description |
|:-------------------------|:----------------------------------------------------------------------------------------------|
+| block_dns_over_https.py | Use mitmproxy to block DNS over HTTPS (DoH) queries |
| change_upstream_proxy.py | Dynamically change the upstream proxy. |
| dns_spoofing.py | Use mitmproxy in a DNS spoofing scenario. |
| dup_and_replay.py | Duplicates each request, changes it, and then replays the modified request. |
diff --git a/examples/complex/block_dns_over_https.py b/examples/complex/block_dns_over_https.py
new file mode 100644
index 00000000..479f0baa
--- /dev/null
+++ b/examples/complex/block_dns_over_https.py
@@ -0,0 +1,177 @@
+"""
+This module is for blocking DNS over HTTPS requests.
+
+It loads a blocklist of IPs and hostnames that are known to serve DNS over HTTPS requests.
+It also uses headers, query params, and paths to detect DoH (and block it)
+"""
+from typing import List
+
+from mitmproxy import ctx
+
+# known DoH providers' hostnames and IP addresses to block
+default_blocklist: dict = {
+ "hostnames": [
+ "dns.adguard.com", "dns-family.adguard.com", "dns.google", "cloudflare-dns.com",
+ "mozilla.cloudflare-dns.com", "security.cloudflare-dns.com", "family.cloudflare-dns.com",
+ "dns.quad9.net", "dns9.quad9.net", "dns10.quad9.net", "dns11.quad9.net", "doh.opendns.com",
+ "doh.familyshield.opendns.com", "doh.cleanbrowsing.org", "doh.xfinity.com", "dohdot.coxlab.net",
+ "odvr.nic.cz", "doh.dnslify.com", "dns.nextdns.io", "dns.dnsoverhttps.net", "doh.crypto.sx",
+ "doh.powerdns.org", "doh-fi.blahdns.com", "doh-jp.blahdns.com", "doh-de.blahdns.com",
+ "doh.ffmuc.net", "dns.dns-over-https.com", "doh.securedns.eu", "dns.rubyfish.cn",
+ "dns.containerpi.com", "dns.containerpi.com", "dns.containerpi.com", "doh-2.seby.io",
+ "doh.seby.io", "commons.host", "doh.dnswarden.com", "doh.dnswarden.com", "doh.dnswarden.com",
+ "dns-nyc.aaflalo.me", "dns.aaflalo.me", "doh.applied-privacy.net", "doh.captnemo.in",
+ "doh.tiar.app", "doh.tiarap.org", "doh.dns.sb", "rdns.faelix.net", "doh.li", "doh.armadillodns.net",
+ "jp.tiar.app", "jp.tiarap.org", "doh.42l.fr", "dns.hostux.net", "dns.hostux.net", "dns.aa.net.uk",
+ "adblock.mydns.network", "ibksturm.synology.me", "jcdns.fun", "ibuki.cgnat.net", "dns.twnic.tw",
+ "example.doh.blockerdns.com", "dns.digitale-gesellschaft.ch", "doh.libredns.gr",
+ "doh.centraleu.pi-dns.com", "doh.northeu.pi-dns.com", "doh.westus.pi-dns.com",
+ "doh.eastus.pi-dns.com", "dns.flatuslifir.is", "private.canadianshield.cira.ca",
+ "protected.canadianshield.cira.ca", "family.canadianshield.cira.ca", "dns.google.com",
+ "dns.google.com"
+ ],
+ "ips": [
+ "176.103.130.131", "176.103.130.130", "2a00:5a60::ad1:ff", "2a00:5a60::ad2:ff", "176.103.130.134", "176.103.130.132",
+ "2a00:5a60::bad2:ff", "2a00:5a60::bad1:ff", "8.8.4.4", "8.8.8.8", "2001:4860:4860::8888", "2001:4860:4860::8844",
+ "104.16.248.249", "104.16.249.249", "2606:4700::6810:f8f9", "2606:4700::6810:f9f9", "104.16.248.249", "104.16.249.249",
+ "2606:4700::6810:f9f9", "2606:4700::6810:f8f9", "104.18.2.55", "104.18.3.55", "2606:4700::6812:337", "2606:4700::6812:237",
+ "104.18.27.128", "104.18.26.128", "2606:4700::6812:1a80", "2606:4700::6812:1b80", "9.9.9.9", "149.112.112.112", "2620:fe::9",
+ "2620:fe::fe", "9.9.9.9", "149.112.112.9", "2620:fe::fe:9", "2620:fe::9", "9.9.9.10", "149.112.112.10", "2620:fe::10",
+ "2620:fe::fe:10", "9.9.9.11", "149.112.112.11", "2620:fe::fe:11", "2620:fe::11", "146.112.41.2", "2620:119:fc::2",
+ "146.112.41.3", "2620:119:fc::3", "185.228.168.168", "185.228.168.10", "96.113.151.148", "2001:558:fe21:6b:96:113:151:149",
+ "174.68.248.77", "185.43.135.1", "2001:148f:fffe::1", "185.235.81.1", "2a0d:4d00:81::1", "45.90.28.0", "2a07:a8c0::",
+ "104.236.178.232", "2604:a880:1:20::51:f001", "104.28.1.106", "104.28.0.106", "2606:4700:3036::681c:6a",
+ "2606:4700:3034::681c:16a", "136.144.215.158", "2a01:7c8:d002:1ef:5054:ff:fe40:3703", "95.216.212.177",
+ "2a01:4f9:c010:43ce::1", "45.32.55.94", "2001:19f0:7001:3259:5400:2ff:fe71:bc9", "159.69.198.101", "2a01:4f8:1c1c:6b4b::1",
+ "195.30.94.28", "2001:608:a01::3", "104.24.122.53", "104.24.123.53", "2606:4700:3033::6818:7b35", "2606:4700:3035::6818:7a35",
+ "146.185.167.43", "2a03:b0c0:0:1010::e9a:3001", "115.159.131.230", "45.77.180.10", "2001:19f0:7001:5554:5400:2ff:fe57:3077",
+ "45.77.180.10", "2001:19f0:7001:5554:5400:2ff:fe57:3077", "45.77.180.10", "2001:19f0:7001:5554:5400:2ff:fe57:3077",
+ "139.99.222.72", "45.76.113.31", "104.182.57.196", "168.235.81.167", "2604:180:f3::42", "176.56.236.175", "2a00:d880:5:bf0::7c93",
+ "94.130.106.88", "2a03:4000:38:53c::2", "139.59.48.222", "174.138.29.175", "2400:6180:0:d0::5f73:4001", "104.18.45.204",
+ "104.18.44.204", "2606:4700:3033::6812:2dcc", "2606:4700:3033::6812:2ccc", "104.31.91.138", "104.31.90.138",
+ "2606:4700:3035::681f:5a8a", "2606:4700:3036::681f:5b8a", "185.134.196.54", "46.227.200.55", "46.227.200.54", "185.134.197.54",
+ "2a01:9e00::54", "2a01:9e01::54", "2a01:9e00::55", "2a01:9e01::55", "46.101.66.244", "172.104.93.80",
+ "2400:8902::f03c:91ff:feda:c514", "104.18.44.204", "104.18.45.204", "2606:4700:3033::6812:2ccc", "2606:4700:3033::6812:2dcc",
+ "185.216.27.142", "185.26.126.37", "2001:4b98:dc2:43:216:3eff:fe86:1d28", "185.26.126.37", "2001:4b98:dc2:43:216:3eff:fe86:1d28",
+ "217.169.20.22", "217.169.20.23", "2001:8b0::2022", "2001:8b0::2023", "172.65.3.223", "2606:4700:60:0:a71e:6467:cef8:2a56",
+ "83.77.85.7", "2a02:1205:34d5:5070:b26e:bfff:fe1d:e19b", "178.62.214.105", "35.198.2.76", "210.17.9.228",
+ "2001:c50:ffff:1:101:101:101:101", "35.231.247.227", "185.95.218.43", "185.95.218.42", "2a05:fc84::43", "2a05:fc84::42",
+ "116.203.115.192", "116.202.176.26", "2a01:4f8:c2c:52bf::1", "88.198.91.187", "2a01:4f8:1c0c:8233::1", "95.216.181.228",
+ "2a01:4f9:c01f:4::abcd", "45.67.219.208", "2a04:bdc7:100:70::abcd", "185.213.26.187", "2a0d:5600:33:3::abcd", "46.239.223.80",
+ "2001:678:888:69:c45d:2738:c3f2:1878", "149.112.121.10", "149.112.122.10", "2620:10a:80bb::10", "2620:10a:80bc::10",
+ "149.112.121.20", "149.112.122.20", "2620:10a:80bb::20", "2620:10a:80bc::20", "149.112.121.30", "149.112.122.30",
+ "2620:10a:80bc::30", "2620:10a:80bb::30"
+ ]
+}
+
+# additional hostnames to block
+additional_doh_names: List[str] = [
+ 'dns.google.com'
+]
+
+# additional IPs to block
+additional_doh_ips: List[str] = [
+
+]
+
+doh_hostnames, doh_ips = default_blocklist['hostnames'], default_blocklist['ips']
+
+# convert to sets for faster lookups
+doh_hostnames = set(doh_hostnames)
+doh_ips = set(doh_ips)
+
+
+def _has_dns_message_content_type(flow):
+ """
+ Check if HTTP request has a DNS-looking 'Content-Type' header
+
+ :param flow: mitmproxy flow
+ :return: True if 'Content-Type' header is DNS-looking, False otherwise
+ """
+ doh_content_types = ['application/dns-message']
+ if 'Content-Type' in flow.request.headers:
+ if flow.request.headers['Content-Type'] in doh_content_types:
+ return True
+ return False
+
+
+def _request_has_dns_query_string(flow):
+ """
+ Check if the query string of a request contains the parameter 'dns'
+
+ :param flow: mitmproxy flow
+ :return: True is 'dns' is a parameter in the query string, False otherwise
+ """
+ return 'dns' in flow.request.query
+
+
+def _request_is_dns_json(flow):
+ """
+ Check if the request looks like DoH with JSON.
+
+ The only known implementations of DoH with JSON are Cloudflare and Google.
+
+ For more info, see:
+ - https://developers.cloudflare.com/1.1.1.1/dns-over-https/json-format/
+ - https://developers.google.com/speed/public-dns/docs/doh/json
+
+ :param flow: mitmproxy flow
+ :return: True is request looks like DNS JSON, False otherwise
+ """
+ # Header 'Accept: application/dns-json' is required in Cloudflare's DoH JSON API
+ # or they return a 400 HTTP response code
+ if 'Accept' in flow.request.headers:
+ if flow.request.headers['Accept'] == 'application/dns-json':
+ return True
+ # Google's DoH JSON API is https://dns.google/resolve
+ path = flow.request.path.split('?')[0]
+ if flow.request.host == 'dns.google' and path == '/resolve':
+ return True
+ return False
+
+
+def _request_has_doh_looking_path(flow):
+ """
+ Check if the path looks like it's DoH.
+ Most common one is '/dns-query', likely because that's what's in the RFC
+
+ :param flow: mitmproxy flow
+ :return: True if path looks like it's DoH, otherwise False
+ """
+ doh_paths = [
+ '/dns-query', # used in example in RFC 8484 (see https://tools.ietf.org/html/rfc8484#section-4.1.1)
+ ]
+ path = flow.request.path.split('?')[0]
+ return path in doh_paths
+
+
+def _requested_hostname_is_in_doh_blacklist(flow):
+ """
+ Check if server hostname is in our DoH provider blacklist.
+
+ The current blacklist is taken from https://github.com/curl/curl/wiki/DNS-over-HTTPS.
+
+ :param flow: mitmproxy flow
+ :return: True if server's hostname is in DoH blacklist, otherwise False
+ """
+ hostname = flow.request.host
+ ip = flow.server_conn.address
+ return hostname in doh_hostnames or hostname in doh_ips or ip in doh_ips
+
+
+doh_request_detection_checks = [
+ _has_dns_message_content_type,
+ _request_has_dns_query_string,
+ _request_is_dns_json,
+ _requested_hostname_is_in_doh_blacklist,
+ _request_has_doh_looking_path
+]
+
+
+def request(flow):
+ for check in doh_request_detection_checks:
+ is_doh = check(flow)
+ if is_doh:
+ ctx.log.warn("[DoH Detection] DNS over HTTPS request detected via method \"%s\"" % check.__name__)
+ flow.kill()
+ break
diff --git a/examples/complex/nonblocking.py b/examples/complex/nonblocking.py
index 72c9c0ab..dd74aec0 100644
--- a/examples/complex/nonblocking.py
+++ b/examples/complex/nonblocking.py
@@ -1,12 +1,11 @@
import time
from mitmproxy.script import concurrent
-from mitmproxy import ctx
@concurrent # Remove this and see what happens
def request(flow):
- # You don't want to use mitmproxy.ctx from a different thread
- ctx.log.info("handle request: %s%s" % (flow.request.host, flow.request.path))
+ # This is ugly in mitmproxy's UI, but you don't want to use mitmproxy.ctx.log from a different thread.
+ print("handle request: %s%s" % (flow.request.host, flow.request.path))
time.sleep(5)
- ctx.log.info("start request: %s%s" % (flow.request.host, flow.request.path))
+ print("start request: %s%s" % (flow.request.host, flow.request.path))
diff --git a/examples/complex/websocket_inject_message.py b/examples/complex/websocket_inject_message.py
index e9c3ea0c..38be5555 100644
--- a/examples/complex/websocket_inject_message.py
+++ b/examples/complex/websocket_inject_message.py
@@ -13,7 +13,7 @@ class InjectWebSocketMessage:
i = 0
while not flow.ended and not flow.error:
await asyncio.sleep(5)
- flow.inject_message(flow.client_conn, 'This is the #{} an injected message!'.format(i))
+ flow.inject_message(flow.client_conn, 'This is the #{} injected message!'.format(i))
i += 1
def websocket_start(self, flow):
diff --git a/examples/simple/websocket_messages.py b/examples/simple/websocket_messages.py
index 719e7b10..071ea21f 100644
--- a/examples/simple/websocket_messages.py
+++ b/examples/simple/websocket_messages.py
@@ -6,8 +6,15 @@ def websocket_message(flow):
# get the latest message
message = flow.messages[-1]
- # simply print the content of the message
- ctx.log.info(message.content)
+ # was the message sent from the client or server?
+ if message.from_client:
+ ctx.log.info("Client sent a message: {}".format(message.content))
+ else:
+ ctx.log.info("Server sent a message: {}".format(message.content))
# manipulate the message content
message.content = re.sub(r'^Hello', 'HAPPY', message.content)
+
+ if 'FOOBAR' in message.content:
+ # kill the message and not send it to the other endpoint
+ message.kill()
diff --git a/examples/simple/wsgi_flask_app.py b/examples/simple/wsgi_flask_app.py
index 4be38000..bbde6913 100644
--- a/examples/simple/wsgi_flask_app.py
+++ b/examples/simple/wsgi_flask_app.py
@@ -14,12 +14,12 @@ def hello_world() -> str:
return 'Hello World!'
-def load(l):
+addons = [
# Host app at the magic domain "proxapp.local" on port 80. Requests to this
# domain and port combination will now be routed to the WSGI app instance.
- return wsgiapp.WSGIApp(app, "proxapp.local", 80)
-
+ wsgiapp.WSGIApp(app, "proxapp.local", 80)
# SSL works too, but the magic domain needs to be resolvable from the mitmproxy machine due to mitmproxy's design.
# mitmproxy will connect to said domain and use serve its certificate (unless --no-upstream-cert is set)
# but won't send any data.
# mitmproxy.ctx.master.apps.add(app, "example.com", 443)
+]
diff --git a/mitmproxy/__init__.py b/mitmproxy/__init__.py
index e69de29b..e930cf1e 100644
--- a/mitmproxy/__init__.py
+++ b/mitmproxy/__init__.py
@@ -0,0 +1,8 @@
+import asyncio
+import sys
+
+if sys.platform == 'win32':
+ # workaround for
+ # https://github.com/tornadoweb/tornado/issues/2751
+ # https://www.tornadoweb.org/en/stable/index.html#installation
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
diff --git a/mitmproxy/addons/browser.py b/mitmproxy/addons/browser.py
index e74b50db..5735698b 100644
--- a/mitmproxy/addons/browser.py
+++ b/mitmproxy/addons/browser.py
@@ -73,4 +73,4 @@ class Browser:
self.browser.kill()
self.tdir.cleanup()
self.browser = None
- self.tdir = None
+ self.tdir = None \ No newline at end of file
diff --git a/mitmproxy/addons/onboardingapp/templates/index.html b/mitmproxy/addons/onboardingapp/templates/index.html
index aa471668..aee6858c 100644
--- a/mitmproxy/addons/onboardingapp/templates/index.html
+++ b/mitmproxy/addons/onboardingapp/templates/index.html
@@ -135,20 +135,20 @@ function changeTo(device) {
<h2 class="text-center"> Click to install your mitmproxy certificate </h2>
<div id="certbank" class="row">
<div class="col-md-3">
- <a target="_blank" onclick="changeTo('apple')" href="/cert/pem"><i class="fa fa-apple fa-5x"></i></a>
- <p>Apple</p>
+ <a target="_blank" onclick="changeTo('apple')" href="/cert/pem"><i class="fa fa-apple fa-5x"></i>
+ <p>Apple</p></a>
</div>
<div class="col-md-3">
- <a target="_blank" onclick="changeTo('windows')" href="/cert/p12"><i class="fa fa-windows fa-5x"></i></a>
- <p>Windows</p>
+ <a target="_blank" onclick="changeTo('windows')" href="/cert/p12"><i class="fa fa-windows fa-5x"></i>
+ <p>Windows</p></a>
</div>
<div class="col-md-3">
- <a target="_blank" onclick="changeTo('android')" href="/cert/pem"><i class="fa fa-android fa-5x"></i></a>
- <p>Android</p>
+ <a target="_blank" onclick="changeTo('android')" href="/cert/pem"><i class="fa fa-android fa-5x"></i>
+ <p>Android</p></a>
</div>
<div class="col-md-3">
- <a target="_blank" onclick="changeTo('asterisk')" href="/cert/pem"><i class="fa fa-asterisk fa-5x"></i></a>
- <p>Other</p>
+ <a target="_blank" onclick="changeTo('asterisk')" href="/cert/pem"><i class="fa fa-asterisk fa-5x"></i>
+ <p>Other</p></a>
</div>
</div>
diff --git a/mitmproxy/addons/script.py b/mitmproxy/addons/script.py
index 3b2568c9..b3cb0999 100644
--- a/mitmproxy/addons/script.py
+++ b/mitmproxy/addons/script.py
@@ -105,11 +105,14 @@ class Script:
# We're already running, so we have to explicitly register and
# configure the addon
ctx.master.addons.invoke_addon(self.ns, "running")
- ctx.master.addons.invoke_addon(
- self.ns,
- "configure",
- ctx.options.keys()
- )
+ try:
+ ctx.master.addons.invoke_addon(
+ self.ns,
+ "configure",
+ ctx.options.keys()
+ )
+ except exceptions.OptionsError as e:
+ script_error_handler(self.fullpath, e, msg=str(e))
async def watcher(self):
last_mtime = 0
diff --git a/mitmproxy/certs.py b/mitmproxy/certs.py
index d574c027..a37d29bc 100644
--- a/mitmproxy/certs.py
+++ b/mitmproxy/certs.py
@@ -15,8 +15,8 @@ import OpenSSL
from mitmproxy.coretypes import serializable
# Default expiry must not be too long: https://github.com/mitmproxy/mitmproxy/issues/815
-DEFAULT_EXP = 94608000 # = 24 * 60 * 60 * 365 * 3
-DEFAULT_EXP_DUMMY_CERT = 63072000 # = 2 years
+DEFAULT_EXP = 94608000 # = 60 * 60 * 24 * 365 * 3 = 3 years
+DEFAULT_EXP_DUMMY_CERT = 31536000 # = 60 * 60 * 24 * 365 = 1 year
# Generated with "openssl dhparam". It's too slow to generate this on startup.
DEFAULT_DHPARAM = b"""
diff --git a/mitmproxy/command.py b/mitmproxy/command.py
index 48f9051e..49cb959e 100644
--- a/mitmproxy/command.py
+++ b/mitmproxy/command.py
@@ -103,7 +103,7 @@ class Command:
except TypeError:
expected = f'Expected: {str(self.signature.parameters)}'
received = f'Received: {str(args)}'
- raise exceptions.CommandError(f"Command argument mismatch: \n\t{expected}\n\t{received}")
+ raise exceptions.CommandError(f"Command argument mismatch: \n {expected}\n {received}")
for name, value in bound_arguments.arguments.items():
convert_to = self.signature.parameters[name].annotation
@@ -149,9 +149,10 @@ class CommandManager:
if not i.startswith("__"):
o = getattr(addon, i)
try:
- is_command = hasattr(o, "command_name")
+ # hasattr is not enough, see https://github.com/mitmproxy/mitmproxy/issues/3794
+ is_command = isinstance(getattr(o, "command_name", None), str)
except Exception:
- pass # hasattr may raise if o implements __getattr__.
+ pass # getattr may raise if o implements __getattr__.
else:
if is_command:
try:
@@ -241,7 +242,7 @@ class CommandManager:
raise exceptions.CommandError("Unknown command: %s" % command_name)
return self.commands[command_name].func(*args)
- def _call_strings(self, command_name: str, args: typing.Sequence[str]) -> typing.Any:
+ def call_strings(self, command_name: str, args: typing.Sequence[str]) -> typing.Any:
"""
Call a command using a list of string arguments. May raise CommandError.
"""
@@ -262,7 +263,7 @@ class CommandManager:
for part in parts
if part.type != mitmproxy.types.Space
]
- return self._call_strings(command_name, args)
+ return self.call_strings(command_name, args)
def dump(self, out=sys.stdout) -> None:
cmds = list(self.commands.values())
@@ -284,7 +285,7 @@ def parsearg(manager: CommandManager, spec: str, argtype: type) -> typing.Any:
try:
return t.parse(manager, argtype, spec)
except exceptions.TypeError as e:
- raise exceptions.CommandError from e
+ raise exceptions.CommandError(str(e)) from e
def command(name: typing.Optional[str] = None):
diff --git a/mitmproxy/net/check.py b/mitmproxy/net/check.py
index a19ad6fe..ffb5e163 100644
--- a/mitmproxy/net/check.py
+++ b/mitmproxy/net/check.py
@@ -2,7 +2,8 @@ import ipaddress
import re
# Allow underscore in host name
-_label_valid = re.compile(br"(?!-)[A-Z\d\-_]{1,63}(?<!-)$", re.IGNORECASE)
+# Note: This could be a DNS label, a hostname, a FQDN, or an IP
+_label_valid = re.compile(br"[A-Z\d\-_]{1,63}$", re.IGNORECASE)
def is_valid_host(host: bytes) -> bool:
diff --git a/mitmproxy/net/http/headers.py b/mitmproxy/net/http/headers.py
index 8a58cbbc..baed7e06 100644
--- a/mitmproxy/net/http/headers.py
+++ b/mitmproxy/net/http/headers.py
@@ -162,8 +162,12 @@ class Headers(multidict.MultiDict):
pattern = re.compile(pattern, flags)
replacements = 0
flag_count = count > 0
+ count_reached = False
fields = []
for name, value in self.fields:
+ if count_reached:
+ fields.append((name, value))
+ continue
line, n = pattern.subn(repl, name + b": " + value, count=count)
try:
name, value = line.split(b": ", 1)
@@ -173,10 +177,12 @@ class Headers(multidict.MultiDict):
pass
else:
replacements += n
+ fields.append((name, value))
if flag_count:
count -= n
if count == 0:
- break
+ count_reached = True
+ continue
fields.append((name, value))
self.fields = tuple(fields)
return replacements
diff --git a/mitmproxy/net/http/http1/assemble.py b/mitmproxy/net/http/http1/assemble.py
index 8b7246f7..d30a74a1 100644
--- a/mitmproxy/net/http/http1/assemble.py
+++ b/mitmproxy/net/http/http1/assemble.py
@@ -1,4 +1,3 @@
-import mitmproxy.net.http.url
from mitmproxy import exceptions
@@ -78,15 +77,7 @@ def _assemble_request_headers(request_data):
Args:
request_data (mitmproxy.net.http.request.RequestData)
"""
- headers = request_data.headers
- if "host" not in headers and request_data.scheme and request_data.host and request_data.port:
- headers = headers.copy()
- headers["host"] = mitmproxy.net.http.url.hostport(
- request_data.scheme,
- request_data.host,
- request_data.port
- )
- return bytes(headers)
+ return bytes(request_data.headers)
def _assemble_response_line(response_data):
diff --git a/mitmproxy/net/http/http1/read.py b/mitmproxy/net/http/http1/read.py
index 294e8358..0fc03f1b 100644
--- a/mitmproxy/net/http/http1/read.py
+++ b/mitmproxy/net/http/http1/read.py
@@ -2,6 +2,8 @@ import time
import sys
import re
+import typing
+
from mitmproxy.net.http import request
from mitmproxy.net.http import response
from mitmproxy.net.http import headers
@@ -171,8 +173,15 @@ def connection_close(http_version, headers):
return http_version != "HTTP/1.1" and http_version != b"HTTP/1.1"
-def expected_http_body_size(request, response=None):
+def expected_http_body_size(
+ request: request.Request,
+ response: typing.Optional[response.Response] = None,
+ expect_continue_as_0: bool = True
+):
"""
+ Args:
+ - expect_continue_as_0: If true, incorrectly predict a body size of 0 for requests which are waiting
+ for a 100 Continue response.
Returns:
The expected body length:
- a positive integer, if the size is known in advance
@@ -186,24 +195,17 @@ def expected_http_body_size(request, response=None):
# http://tools.ietf.org/html/rfc7230#section-3.3
if not response:
headers = request.headers
- response_code = None
- is_request = True
- else:
- headers = response.headers
- response_code = response.status_code
- is_request = False
-
- if is_request:
- if headers.get("expect", "").lower() == "100-continue":
+ if expect_continue_as_0 and headers.get("expect", "").lower() == "100-continue":
return 0
else:
+ headers = response.headers
if request.method.upper() == "HEAD":
return 0
- if 100 <= response_code <= 199:
+ if 100 <= response.status_code <= 199:
return 0
- if response_code == 200 and request.method.upper() == "CONNECT":
+ if response.status_code == 200 and request.method.upper() == "CONNECT":
return 0
- if response_code in (204, 304):
+ if response.status_code in (204, 304):
return 0
if "chunked" in headers.get("transfer-encoding", "").lower():
@@ -218,9 +220,9 @@ def expected_http_body_size(request, response=None):
if size < 0:
raise ValueError()
return size
- except ValueError:
- raise exceptions.HttpSyntaxException("Unparseable Content Length")
- if is_request:
+ except ValueError as e:
+ raise exceptions.HttpSyntaxException("Unparseable Content Length") from e
+ if not response:
return 0
return -1
diff --git a/mitmproxy/net/server_spec.py b/mitmproxy/net/server_spec.py
index efbf1012..c877e6e1 100644
--- a/mitmproxy/net/server_spec.py
+++ b/mitmproxy/net/server_spec.py
@@ -1,13 +1,17 @@
"""
Parse scheme, host and port from a string.
"""
-import collections
import re
+import typing
from typing import Tuple
from mitmproxy.net import check
-ServerSpec = collections.namedtuple("ServerSpec", ["scheme", "address"])
+
+class ServerSpec(typing.NamedTuple):
+ scheme: str
+ address: typing.Tuple[str, int]
+
server_spec_re = re.compile(
r"""
diff --git a/mitmproxy/net/tls.py b/mitmproxy/net/tls.py
index d68a008f..d8e943d3 100644
--- a/mitmproxy/net/tls.py
+++ b/mitmproxy/net/tls.py
@@ -7,14 +7,13 @@ import os
import struct
import threading
import typing
-from ssl import match_hostname, CertificateError
import certifi
from OpenSSL import SSL
from kaitaistruct import KaitaiStream
-import mitmproxy.options # noqa
-from mitmproxy import exceptions, certs
+import mitmproxy.options
+from mitmproxy import certs, exceptions
from mitmproxy.contrib.kaitaistruct import tls_client_hello
from mitmproxy.net import check
@@ -88,7 +87,18 @@ class MasterSecretLogger:
__name__ = "MasterSecretLogger"
def __call__(self, connection, where, ret):
- if where == SSL.SSL_CB_HANDSHAKE_DONE and ret == 1:
+ done_now = (
+ where == SSL.SSL_CB_HANDSHAKE_DONE and ret == 1
+ )
+ # this is a horrendous workaround for https://github.com/mitmproxy/mitmproxy/pull/3692#issuecomment-608454530:
+ # OpenSSL 1.1.1f decided to not make connection.master_key() fail in the SSL_CB_HANDSHAKE_DONE callback.
+ # To support various OpenSSL versions and still log master secrets, we now mark connections where this has
+ # happened and then try again on the next event. This is ugly and shouldn't be done, but eventually we
+ # replace this with context.set_keylog_callback anyways.
+ done_previously_but_not_logged_yet = (
+ hasattr(connection, "_still_needs_masterkey")
+ )
+ if done_now or done_previously_but_not_logged_yet:
with self.lock:
if not self.f:
d = os.path.dirname(self.filename)
@@ -96,10 +106,16 @@ class MasterSecretLogger:
os.makedirs(d)
self.f = open(self.filename, "ab")
self.f.write(b"\r\n")
- client_random = binascii.hexlify(connection.client_random())
- masterkey = binascii.hexlify(connection.master_key())
- self.f.write(b"CLIENT_RANDOM %s %s\r\n" % (client_random, masterkey))
- self.f.flush()
+ try:
+ client_random = binascii.hexlify(connection.client_random())
+ masterkey = binascii.hexlify(connection.master_key())
+ except (AssertionError, SSL.Error): # careful: exception type changes between pyOpenSSL versions
+ connection._still_needs_masterkey = True
+ else:
+ self.f.write(b"CLIENT_RANDOM %s %s\r\n" % (client_random, masterkey))
+ self.f.flush()
+ if hasattr(connection, "_still_needs_masterkey"):
+ delattr(connection, "_still_needs_masterkey")
def close(self):
with self.lock:
@@ -237,33 +253,11 @@ def create_client_context(
depth: int,
is_cert_verified: bool
) -> bool:
- if is_cert_verified and depth == 0:
- # Verify hostname of leaf certificate.
- cert = certs.Cert(x509)
- try:
- crt: typing.Dict[str, typing.Any] = dict(
- subjectAltName=[("DNS", x.decode("ascii", "strict")) for x in cert.altnames]
- )
- if cert.cn:
- crt["subject"] = [[["commonName", cert.cn.decode("ascii", "strict")]]]
- if sni:
- # SNI hostnames allow support of IDN by using ASCII-Compatible Encoding
- # Conversion algorithm is in RFC 3490 which is implemented by idna codec
- # https://docs.python.org/3/library/codecs.html#text-encodings
- # https://tools.ietf.org/html/rfc6066#section-3
- # https://tools.ietf.org/html/rfc4985#section-3
- hostname = sni.encode("idna").decode("ascii")
- else:
- hostname = "no-hostname"
- match_hostname(crt, hostname)
- except (ValueError, CertificateError) as e:
- conn.cert_error = exceptions.InvalidCertificateException(
- "Certificate verification error for {}: {}".format(
- sni or repr(address),
- str(e)
- )
- )
- is_cert_verified = False
+ if is_cert_verified and depth == 0 and not sni:
+ conn.cert_error = exceptions.InvalidCertificateException(
+ f"Certificate verification error for {address}: Cannot validate hostname, SNI missing."
+ )
+ is_cert_verified = False
elif is_cert_verified:
pass
else:
@@ -285,6 +279,20 @@ def create_client_context(
**sslctx_kwargs,
)
+ if sni:
+ # Manually enable hostname verification on the context object.
+ # https://wiki.openssl.org/index.php/Hostname_validation
+ param = SSL._lib.SSL_CTX_get0_param(context._context)
+ # Matching on the CN is disabled in both Chrome and Firefox, so we disable it, too.
+ # https://www.chromestatus.com/feature/4981025180483584
+ SSL._lib.X509_VERIFY_PARAM_set_hostflags(
+ param,
+ SSL._lib.X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS | SSL._lib.X509_CHECK_FLAG_NEVER_CHECK_SUBJECT
+ )
+ SSL._openssl_assert(
+ SSL._lib.X509_VERIFY_PARAM_set1_host(param, sni.encode("idna"), 0) == 1
+ )
+
# Client Certs
if cert:
try:
diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py
index f42aa645..ac85f83a 100644
--- a/mitmproxy/optmanager.py
+++ b/mitmproxy/optmanager.py
@@ -320,8 +320,7 @@ class OptManager:
update = {}
for optname, optval in self.deferred.items():
if optname in self._options:
- if isinstance(optval, str):
- optval = self.parse_setval(self._options[optname], optval)
+ optval = self.parse_setval(self._options[optname], optval)
update[optname] = optval
self.update(**update)
for k in update.keys():
diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py
index e98faabf..982afb75 100644
--- a/mitmproxy/proxy/config.py
+++ b/mitmproxy/proxy/config.py
@@ -23,7 +23,7 @@ class HostMatcher:
if self.handle in ["ignore", "tcp"]:
return any(rex.search(host) for rex in self.regexes)
else: # self.handle == "allow"
- return any(not rex.search(host) for rex in self.regexes)
+ return not any(rex.search(host) for rex in self.regexes)
def __bool__(self):
return bool(self.patterns)
diff --git a/mitmproxy/proxy/protocol/base.py b/mitmproxy/proxy/protocol/base.py
index 2063f901..3bf03521 100644
--- a/mitmproxy/proxy/protocol/base.py
+++ b/mitmproxy/proxy/protocol/base.py
@@ -160,10 +160,10 @@ class ServerConnectionMixin:
"""
if not self.server_conn.address:
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()
+ self.log("serverconnect", "debug", [repr(self.server_conn.address)])
+ self.channel.ask("serverconnect", self.server_conn)
except exceptions.TcpException as e:
raise exceptions.ProtocolException(
"Server connection to {} failed: {}".format(
diff --git a/mitmproxy/proxy/protocol/http.py b/mitmproxy/proxy/protocol/http.py
index 4c20617b..37e1669e 100644
--- a/mitmproxy/proxy/protocol/http.py
+++ b/mitmproxy/proxy/protocol/http.py
@@ -1,3 +1,5 @@
+import textwrap
+
import h2.exceptions
import time
import enum
@@ -142,13 +144,13 @@ def validate_request_form(mode, request):
allowed_request_forms = MODE_REQUEST_FORMS[mode]
if request.first_line_format not in allowed_request_forms:
if mode == HTTPMode.transparent:
- err_message = (
+ err_message = textwrap.dedent((
"""
Mitmproxy received an {} request even though it is not running
in regular mode. This usually indicates a misconfiguration,
please see the mitmproxy mode documentation for details.
"""
- ).format("HTTP CONNECT" if request.first_line_format == "authority" else "absolute-form")
+ )).strip().format("HTTP CONNECT" if request.first_line_format == "authority" else "absolute-form")
else:
err_message = "Invalid HTTP request form (expected: %s, got: %s)" % (
" or ".join(allowed_request_forms), request.first_line_format
diff --git a/mitmproxy/proxy/protocol/http2.py b/mitmproxy/proxy/protocol/http2.py
index a5870e6c..f5e7b1c6 100644
--- a/mitmproxy/proxy/protocol/http2.py
+++ b/mitmproxy/proxy/protocol/http2.py
@@ -87,6 +87,18 @@ class Http2Layer(base.Layer):
# mypy type hints
client_conn: connections.ClientConnection = None
+ class H2ConnLogger:
+ def __init__(self, name, log):
+ self.name = name
+ self.log = log
+
+ def debug(self, fmtstr, *args):
+ msg = "H2Conn {}: {}".format(self.name, fmtstr % args)
+ self.log(msg, "debug")
+
+ def trace(self, fmtstr, *args):
+ pass
+
def __init__(self, ctx, mode: str) -> None:
super().__init__(ctx)
self.mode = mode
@@ -98,7 +110,8 @@ class Http2Layer(base.Layer):
client_side=False,
header_encoding=False,
validate_outbound_headers=False,
- validate_inbound_headers=False)
+ validate_inbound_headers=False,
+ logger=self.H2ConnLogger("client", self.log))
self.connections[self.client_conn] = SafeH2Connection(self.client_conn, config=config)
def _initiate_server_conn(self):
@@ -107,7 +120,8 @@ class Http2Layer(base.Layer):
client_side=True,
header_encoding=False,
validate_outbound_headers=False,
- validate_inbound_headers=False)
+ validate_inbound_headers=False,
+ logger=self.H2ConnLogger("server", self.log))
self.connections[self.server_conn] = SafeH2Connection(self.server_conn, config=config)
self.connections[self.server_conn].initiate_connection()
self.server_conn.send(self.connections[self.server_conn].data_to_send())
@@ -195,10 +209,12 @@ class Http2Layer(base.Layer):
else:
self.streams[eid].data_queue.put(event.data)
self.streams[eid].queued_data_length += len(event.data)
- self.connections[source_conn].safe_acknowledge_received_data(
- event.flow_controlled_length,
- event.stream_id
- )
+
+ # always acknowledge receved data with a WINDOW_UPDATE frame
+ self.connections[source_conn].safe_acknowledge_received_data(
+ event.flow_controlled_length,
+ event.stream_id
+ )
return True
def _handle_stream_ended(self, eid):
@@ -209,13 +225,12 @@ class Http2Layer(base.Layer):
def _handle_stream_reset(self, eid, event, is_server, other_conn):
if eid in self.streams:
self.streams[eid].kill()
- if event.error_code == h2.errors.ErrorCodes.CANCEL:
- if is_server:
- other_stream_id = self.streams[eid].client_stream_id
- else:
- other_stream_id = self.streams[eid].server_stream_id
- if other_stream_id is not None:
- self.connections[other_conn].safe_reset_stream(other_stream_id, event.error_code)
+ if is_server:
+ other_stream_id = self.streams[eid].client_stream_id
+ else:
+ other_stream_id = self.streams[eid].server_stream_id
+ if other_stream_id is not None:
+ self.connections[other_conn].safe_reset_stream(other_stream_id, event.error_code)
return True
def _handle_remote_settings_changed(self, event, other_conn):
@@ -461,7 +476,7 @@ class Http2SingleStreamLayer(httpbase._HttpTransmissionLayer, basethread.BaseThr
if self.zombie is not None or connection_closed:
if pre_command is not None:
pre_command()
- raise exceptions.Http2ZombieException("Connection already dead")
+ raise exceptions.Http2ZombieException("Connection or stream already dead: {}, {}".format(self.zombie, connection_closed))
@detect_zombie_stream
def read_request_headers(self, flow):
@@ -643,7 +658,8 @@ class Http2SingleStreamLayer(httpbase._HttpTransmissionLayer, basethread.BaseThr
try:
layer()
except exceptions.Http2ZombieException: # pragma: no cover
- pass
+ # zombies can be safely terminated - no need to kill them twice
+ return
except exceptions.ProtocolException as e: # pragma: no cover
self.log(repr(e), "info")
except exceptions.SetServerNotAllowedException as e: # pragma: no cover
diff --git a/mitmproxy/proxy/protocol/rawtcp.py b/mitmproxy/proxy/protocol/rawtcp.py
index 0ec50594..00bba04c 100644
--- a/mitmproxy/proxy/protocol/rawtcp.py
+++ b/mitmproxy/proxy/protocol/rawtcp.py
@@ -29,13 +29,20 @@ class RawTCPLayer(base.Layer):
server = self.server_conn.connection
conns = [client, server]
+ # https://github.com/openssl/openssl/issues/6234
+ for conn in conns:
+ if isinstance(conn, SSL.Connection) and hasattr(SSL._lib, "SSL_clear_mode"):
+ SSL._lib.SSL_clear_mode(conn._ssl, SSL._lib.SSL_MODE_AUTO_RETRY)
+
try:
while not self.channel.should_exit.is_set():
r = mitmproxy.net.tcp.ssl_read_select(conns, 10)
for conn in r:
dst = server if conn == client else client
-
- size = conn.recv_into(buf, self.chunk_size)
+ try:
+ size = conn.recv_into(buf, self.chunk_size)
+ except (SSL.WantReadError, SSL.WantWriteError):
+ continue
if not size:
conns.remove(conn)
# Shutdown connection to the other peer
diff --git a/mitmproxy/tools/_main.py b/mitmproxy/tools/_main.py
index b98bbe90..c1dd6179 100644
--- a/mitmproxy/tools/_main.py
+++ b/mitmproxy/tools/_main.py
@@ -113,12 +113,12 @@ def run(
opts.update(**extra(args))
loop = asyncio.get_event_loop()
- for signame in ('SIGINT', 'SIGTERM'):
- try:
- loop.add_signal_handler(getattr(signal, signame), getattr(master, "prompt_for_exit", master.shutdown))
- except NotImplementedError:
- # Not supported on Windows
- pass
+ try:
+ loop.add_signal_handler(signal.SIGINT, getattr(master, "prompt_for_exit", master.shutdown))
+ loop.add_signal_handler(signal.SIGTERM, master.shutdown)
+ except NotImplementedError:
+ # Not supported on Windows
+ pass
# Make sure that we catch KeyboardInterrupts on Windows.
# https://stackoverflow.com/a/36925722/934719
diff --git a/mitmproxy/tools/cmdline.py b/mitmproxy/tools/cmdline.py
index e9ff973f..9c8eae8a 100644
--- a/mitmproxy/tools/cmdline.py
+++ b/mitmproxy/tools/cmdline.py
@@ -127,7 +127,7 @@ def mitmweb(opts):
group = parser.add_argument_group("Mitmweb")
opts.make_parser(group, "web_open_browser")
opts.make_parser(group, "web_port", metavar="PORT")
- opts.make_parser(group, "web_iface", metavar="INTERFACE")
+ opts.make_parser(group, "web_host", metavar="HOST")
common_options(parser, opts)
group = parser.add_argument_group(
diff --git a/mitmproxy/tools/console/commandexecutor.py b/mitmproxy/tools/console/commandexecutor.py
index c738e349..1c6d5aa6 100644
--- a/mitmproxy/tools/console/commandexecutor.py
+++ b/mitmproxy/tools/console/commandexecutor.py
@@ -2,6 +2,7 @@ import typing
from mitmproxy import exceptions
from mitmproxy import flow
+from mitmproxy import ctx
from mitmproxy.tools.console import overlay
from mitmproxy.tools.console import signals
@@ -15,8 +16,8 @@ class CommandExecutor:
if cmd.strip():
try:
ret = self.master.commands.execute(cmd)
- except exceptions.CommandError as v:
- signals.status_message.send(message=str(v))
+ except exceptions.CommandError as e:
+ ctx.log.error(str(e))
else:
if ret:
if type(ret) == typing.Sequence[flow.Flow]:
diff --git a/mitmproxy/tools/console/common.py b/mitmproxy/tools/console/common.py
index 325c5740..3dce8363 100644
--- a/mitmproxy/tools/console/common.py
+++ b/mitmproxy/tools/console/common.py
@@ -543,7 +543,7 @@ def format_flow(f, focus, extended=False, hostheader=False, cols=False, layout='
duration = None
if f.response.timestamp_end and f.request.timestamp_start:
- duration = f.response.timestamp_end - f.request.timestamp_start
+ duration = max([f.response.timestamp_end - f.request.timestamp_start, 0])
d.update(dict(
resp_code=f.response.status_code,
diff --git a/mitmproxy/tools/console/consoleaddons.py b/mitmproxy/tools/console/consoleaddons.py
index 7fcd9b48..905653e7 100644
--- a/mitmproxy/tools/console/consoleaddons.py
+++ b/mitmproxy/tools/console/consoleaddons.py
@@ -160,7 +160,7 @@ class ConsoleAddon:
fv = self.master.window.current("options")
if not fv:
raise exceptions.CommandError("Not viewing options.")
- self.master.commands.execute("options.reset.one %s" % fv.current_name())
+ self.master.commands.call_strings("options.reset.one", [fv.current_name()])
@command.command("console.nav.start")
def nav_start(self) -> None:
@@ -248,12 +248,11 @@ class ConsoleAddon:
def callback(opt):
# We're now outside of the call context...
- repl = cmd + " " + " ".join(args)
- repl = repl.replace("{choice}", opt)
+ repl = [arg.replace("{choice}", opt) for arg in args]
try:
- self.master.commands.execute(repl)
+ self.master.commands.call_strings(cmd, repl)
except exceptions.CommandError as e:
- signals.status_message.send(message=str(e))
+ ctx.log.error(str(e))
self.master.overlay(
overlay.Chooser(self.master, prompt, choices, "", callback)
@@ -276,12 +275,11 @@ class ConsoleAddon:
def callback(opt):
# We're now outside of the call context...
- repl = " ".join(command_lexer.quote(x) for x in args)
- repl = repl.replace("{choice}", opt)
+ repl = [arg.replace("{choice}", opt) for arg in args]
try:
- self.master.commands.execute(subcmd + " " + repl)
+ self.master.commands.call_strings(subcmd, repl)
except exceptions.CommandError as e:
- signals.status_message.send(message=str(e))
+ ctx.log.error(str(e))
self.master.overlay(
overlay.Chooser(self.master, prompt, choices, "", callback)
@@ -454,8 +452,9 @@ class ConsoleAddon:
url = edited_url.rstrip(b"\n")
flow.request.url = url.decode()
elif flow_part in ["method", "status_code", "reason"]:
- self.master.commands.execute(
- "console.command flow.set @focus %s " % flow_part
+ self.master.commands.call_strings(
+ "console.command",
+ ["flow.set", "@focus", flow_part]
)
def _grideditor(self):
@@ -539,10 +538,12 @@ class ConsoleAddon:
raise exceptions.CommandError("Invalid flowview mode.")
try:
- cmd = 'view.settings.setval @focus flowview_mode_%s %s' % (idx, mode)
- self.master.commands.execute(cmd)
+ self.master.commands.call_strings(
+ "view.settings.setval",
+ ["@focus", "flowview_mode_%s" % (idx,), mode]
+ )
except exceptions.CommandError as e:
- signals.status_message.send(message=str(e))
+ ctx.log.error(str(e))
@command.command("console.flowview.mode.options")
def flowview_mode_options(self) -> typing.Sequence[str]:
@@ -561,8 +562,10 @@ class ConsoleAddon:
raise exceptions.CommandError("Not viewing a flow.")
idx = fv.body.tab_offset
- cmd = 'view.settings.getval @focus flowview_mode_%s %s' % (idx, self.master.options.console_default_contentview)
- return self.master.commands.execute(cmd)
+ return self.master.commands.call_strings(
+ "view.settings.getval",
+ ["@focus", "flowview_mode_%s" % (idx,), self.master.options.console_default_contentview]
+ )
@command.command("console.key.contexts")
def key_contexts(self) -> typing.Sequence[str]:
diff --git a/mitmproxy/tools/console/keymap.py b/mitmproxy/tools/console/keymap.py
index 01ec9a0a..a42225cf 100644
--- a/mitmproxy/tools/console/keymap.py
+++ b/mitmproxy/tools/console/keymap.py
@@ -202,7 +202,7 @@ class KeymapConfig:
user_ctxs = v.get("ctx", ["global"])
try:
km._check_contexts(user_ctxs)
- km.remove(v["key"], Contexts)
+ km.remove(v["key"], user_ctxs)
km.add(
key = v["key"],
command = v["cmd"],
diff --git a/mitmproxy/tools/web/master.py b/mitmproxy/tools/web/master.py
index e2c60d94..d127bdec 100644
--- a/mitmproxy/tools/web/master.py
+++ b/mitmproxy/tools/web/master.py
@@ -1,5 +1,3 @@
-import webbrowser
-
import tornado.httpserver
import tornado.ioloop
from tornado.platform.asyncio import AsyncIOMainLoop
@@ -107,43 +105,9 @@ class WebMaster(master.Master):
AsyncIOMainLoop().install()
iol = tornado.ioloop.IOLoop.instance()
http_server = tornado.httpserver.HTTPServer(self.app)
- http_server.listen(self.options.web_port, self.options.web_iface)
- web_url = "http://{}:{}/".format(self.options.web_iface, self.options.web_port)
+ http_server.listen(self.options.web_port, self.options.web_host)
+ web_url = "http://{}:{}/".format(self.options.web_host, self.options.web_port)
self.log.info(
"Web server listening at {}".format(web_url),
)
- # FIXME: This should be in an addon hooked to the "running" event, not in master
- if self.options.web_open_browser:
- success = open_browser(web_url)
- if not success:
- self.log.info(
- "No web browser found. Please open a browser and point it to {}".format(web_url),
- )
self.run_loop(iol.start)
-
-
-def open_browser(url: str) -> bool:
- """
- Open a URL in a browser window.
- In contrast to webbrowser.open, we limit the list of suitable browsers.
- This gracefully degrades to a no-op on headless servers, where webbrowser.open
- would otherwise open lynx.
-
- Returns:
- True, if a browser has been opened
- False, if no suitable browser has been found.
- """
- browsers = (
- "windows-default", "macosx",
- "google-chrome", "chrome", "chromium", "chromium-browser",
- "firefox", "opera", "safari",
- )
- for browser in browsers:
- try:
- b = webbrowser.get(browser)
- except webbrowser.Error:
- pass
- else:
- b.open(url)
- return True
- return False
diff --git a/mitmproxy/tools/web/static/app.js b/mitmproxy/tools/web/static/app.js
index e429a344..d5888309 100644
--- a/mitmproxy/tools/web/static/app.js
+++ b/mitmproxy/tools/web/static/app.js
@@ -20,7 +20,7 @@
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function ContentViewOptions(e){var t=e.flow,o=e.message,n=e.uploadContent,r=e.readonly,a=e.contentViewDescription;return _react2.default.createElement("div",{className:"view-options"},r?_react2.default.createElement(_ViewSelector2.default,{message:o}):_react2.default.createElement("span",null,_react2.default.createElement("b",null,"View:")," edit")," ",_react2.default.createElement(_DownloadContentButton2.default,{flow:t,message:o})," ",!r&&_react2.default.createElement(_UploadContentButton2.default,{uploadContent:n})," ",r&&_react2.default.createElement("span",null,a))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes),_reactRedux=require("react-redux"),_ViewSelector=require("./ViewSelector"),_ViewSelector2=_interopRequireDefault(_ViewSelector),_UploadContentButton=require("./UploadContentButton"),_UploadContentButton2=_interopRequireDefault(_UploadContentButton),_DownloadContentButton=require("./DownloadContentButton"),_DownloadContentButton2=_interopRequireDefault(_DownloadContentButton);ContentViewOptions.propTypes={flow:_propTypes2.default.object.isRequired,message:_propTypes2.default.object.isRequired},exports.default=(0,_reactRedux.connect)(function(e){return{contentViewDescription:e.ui.flow.viewDescription,readonly:!e.ui.flow.modifiedFlow}})(ContentViewOptions);
},{"./DownloadContentButton":9,"./UploadContentButton":12,"./ViewSelector":13,"prop-types":"prop-types","react":"react","react-redux":"react-redux"}],8:[function(require,module,exports){
-"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function ViewImage(e){var t=e.flow,r=e.message;return _react2.default.createElement("div",{className:"flowview-image"},_react2.default.createElement("img",{src:_utils.MessageUtils.getContentURL(t,r),alt:"preview",className:"img-thumbnail"}))}function Edit(e){var t=e.content,r=e.onChange;return _react2.default.createElement(_CodeEditor2.default,{content:t,onChange:r})}Object.defineProperty(exports,"__esModule",{value:!0}),exports.ViewImage=exports.ViewServer=exports.Edit=exports.PureViewServer=void 0;var _slicedToArray=function(){function e(e,t){var r=[],n=!0,o=!1,i=void 0;try{for(var a,s=e[Symbol.iterator]();!(n=(a=s.next()).done)&&(r.push(a.value),!t||r.length!==t);n=!0);}catch(e){o=!0,i=e}finally{try{!n&&s.return&&s.return()}finally{if(o)throw i}}return r}return function(t,r){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,r);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),_createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes),_reactRedux=require("react-redux"),_flow=require("../../ducks/ui/flow"),_ContentLoader=require("./ContentLoader"),_ContentLoader2=_interopRequireDefault(_ContentLoader),_utils=require("../../flow/utils"),_CodeEditor=require("./CodeEditor"),_CodeEditor2=_interopRequireDefault(_CodeEditor),isImage=/^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i;ViewImage.matches=function(e){return isImage.test(_utils.MessageUtils.getContentType(e))},ViewImage.propTypes={flow:_propTypes2.default.object.isRequired,message:_propTypes2.default.object.isRequired},Edit.propTypes={content:_propTypes2.default.string.isRequired},exports.Edit=Edit=(0,_ContentLoader2.default)(Edit);var PureViewServer=exports.PureViewServer=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return _inherits(t,_react.Component),_createClass(t,[{key:"componentWillMount",value:function(){this.setContentView(this.props)}},{key:"componentWillReceiveProps",value:function(e){e.content!=this.props.content&&this.setContentView(e)}},{key:"setContentView",value:function(e){try{this.data=JSON.parse(e.content)}catch(e){this.data={lines:[],description:e.message}}e.setContentViewDescription(e.contentView!=this.data.description?this.data.description:""),e.setContent(this.data.lines)}},{key:"render",value:function(){var e=this.props,t=(e.content,e.contentView,e.message),r=e.maxLines,n=this.props.showFullContent?this.data.lines:this.data.lines.slice(0,r);return _react2.default.createElement("div",null,ViewImage.matches(t)&&_react2.default.createElement(ViewImage,this.props),_react2.default.createElement("pre",null,n.map(function(e,t){return _react2.default.createElement("div",{key:"line"+t},e.map(function(e,t){var r=_slicedToArray(e,2),n=r[0],o=r[1];return _react2.default.createElement("span",{key:"tuple"+t,className:n},o)}))})))}}]),t}();PureViewServer.propTypes={showFullContent:_propTypes2.default.bool.isRequired,maxLines:_propTypes2.default.number.isRequired,setContentViewDescription:_propTypes2.default.func.isRequired,setContent:_propTypes2.default.func.isRequired};var ViewServer=(0,_reactRedux.connect)(function(e){return{showFullContent:e.ui.flow.showFullContent,maxLines:e.ui.flow.maxContentLines}},{setContentViewDescription:_flow.setContentViewDescription,setContent:_flow.setContent})((0,_ContentLoader2.default)(PureViewServer));exports.Edit=Edit,exports.ViewServer=ViewServer,exports.ViewImage=ViewImage;
+"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _possibleConstructorReturn(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function ViewImage(e){var t=e.flow,r=e.message;return _react2.default.createElement("div",{className:"flowview-image"},_react2.default.createElement("img",{src:_utils.MessageUtils.getContentURL(t,r),alt:"preview",className:"img-thumbnail"}))}function Edit(e){var t=e.content,r=e.onChange;return _react2.default.createElement(_CodeEditor2.default,{content:t,onChange:r})}Object.defineProperty(exports,"__esModule",{value:!0}),exports.ViewImage=exports.ViewServer=exports.Edit=exports.PureViewServer=void 0;var _slicedToArray=function(){function e(e,t){var r=[],n=!0,o=!1,i=void 0;try{for(var a,s=e[Symbol.iterator]();!(n=(a=s.next()).done)&&(r.push(a.value),!t||r.length!==t);n=!0);}catch(e){o=!0,i=e}finally{try{!n&&s.return&&s.return()}finally{if(o)throw i}}return r}return function(t,r){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,r);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),_createClass=function(){function e(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,r,n){return r&&e(t.prototype,r),n&&e(t,n),t}}(),_react=require("react"),_react2=_interopRequireDefault(_react),_propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes),_reactRedux=require("react-redux"),_flow=require("../../ducks/ui/flow"),_ContentLoader=require("./ContentLoader"),_ContentLoader2=_interopRequireDefault(_ContentLoader),_utils=require("../../flow/utils"),_CodeEditor=require("./CodeEditor"),_CodeEditor2=_interopRequireDefault(_CodeEditor),isImage=/^image\/(png|jpe?g|gif|webp|vnc.microsoft.icon|x-icon)$/i;ViewImage.matches=function(e){return isImage.test(_utils.MessageUtils.getContentType(e))},ViewImage.propTypes={flow:_propTypes2.default.object.isRequired,message:_propTypes2.default.object.isRequired},Edit.propTypes={content:_propTypes2.default.string.isRequired},exports.Edit=Edit=(0,_ContentLoader2.default)(Edit);var PureViewServer=exports.PureViewServer=function(e){function t(){return _classCallCheck(this,t),_possibleConstructorReturn(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return _inherits(t,_react.Component),_createClass(t,[{key:"componentWillMount",value:function(){this.setContentView(this.props)}},{key:"componentWillReceiveProps",value:function(e){e.content!=this.props.content&&this.setContentView(e)}},{key:"setContentView",value:function(e){try{this.data=JSON.parse(e.content)}catch(e){this.data={lines:[],description:e.message}}e.setContentViewDescription(e.contentView!=this.data.description?this.data.description:""),e.setContent(this.data.lines)}},{key:"render",value:function(){var e=this.props,t=(e.content,e.contentView,e.message),r=e.maxLines,n=this.props.showFullContent?this.data.lines:this.data.lines.slice(0,r);return _react2.default.createElement("div",null,ViewImage.matches(t)&&_react2.default.createElement(ViewImage,this.props),_react2.default.createElement("pre",null,n.map(function(e,t){return _react2.default.createElement("div",{key:"line"+t},e.map(function(e,t){var r=_slicedToArray(e,2),n=r[0],o=r[1];return _react2.default.createElement("span",{key:"tuple"+t,className:n},o)}))})))}}]),t}();PureViewServer.propTypes={showFullContent:_propTypes2.default.bool.isRequired,maxLines:_propTypes2.default.number.isRequired,setContentViewDescription:_propTypes2.default.func.isRequired,setContent:_propTypes2.default.func.isRequired};var ViewServer=(0,_reactRedux.connect)(function(e){return{showFullContent:e.ui.flow.showFullContent,maxLines:e.ui.flow.maxContentLines}},{setContentViewDescription:_flow.setContentViewDescription,setContent:_flow.setContent})((0,_ContentLoader2.default)(PureViewServer));exports.Edit=Edit,exports.ViewServer=ViewServer,exports.ViewImage=ViewImage;
},{"../../ducks/ui/flow":60,"../../flow/utils":68,"./CodeEditor":5,"./ContentLoader":6,"prop-types":"prop-types","react":"react","react-redux":"react-redux"}],9:[function(require,module,exports){
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function DownloadContentButton(e){var t=e.flow,o=e.message;return _react2.default.createElement("a",{className:"btn btn-default btn-xs",href:_utils.MessageUtils.getContentURL(t,o),title:"Download the content of the flow."},_react2.default.createElement("i",{className:"fa fa-download"}))}Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=DownloadContentButton;var _react=require("react"),_react2=_interopRequireDefault(_react),_utils=require("../../flow/utils"),_propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes);DownloadContentButton.propTypes={flow:_propTypes2.default.object.isRequired,message:_propTypes2.default.object.isRequired};
@@ -101,7 +101,7 @@
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _defineProperty(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function MenuToggle(e){var t=e.value,n=e.onChange,r=e.children;return React.createElement("div",{className:"menu-entry"},React.createElement("label",null,React.createElement("input",{type:"checkbox",checked:t,onChange:n}),r))}function SettingsToggle(e){var t=e.setting,n=e.children,r=e.settings,g=e.updateSettings;return React.createElement(MenuToggle,{value:r[t]||!1,onChange:function(){return g(_defineProperty({},t,!r[t]))}},n)}function EventlogToggle(e){var t=e.toggleVisibility,n=e.eventLogVisible;return React.createElement(MenuToggle,{value:n,onChange:t},"Display Event Log")}Object.defineProperty(exports,"__esModule",{value:!0}),exports.MenuToggle=MenuToggle,exports.SettingsToggle=SettingsToggle,exports.EventlogToggle=EventlogToggle;var _propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes),_reactRedux=require("react-redux"),_settings=require("../../ducks/settings"),_eventLog=require("../../ducks/eventLog");MenuToggle.propTypes={value:_propTypes2.default.bool.isRequired,onChange:_propTypes2.default.func.isRequired,children:_propTypes2.default.node.isRequired},SettingsToggle.propTypes={setting:_propTypes2.default.string.isRequired,children:_propTypes2.default.node.isRequired},exports.SettingsToggle=SettingsToggle=(0,_reactRedux.connect)(function(e){return{settings:e.settings}},{updateSettings:_settings.update})(SettingsToggle),exports.EventlogToggle=EventlogToggle=(0,_reactRedux.connect)(function(e){return{eventLogVisible:e.eventLog.visible}},{toggleVisibility:_eventLog.toggleVisibility})(EventlogToggle);
},{"../../ducks/eventLog":55,"../../ducks/settings":59,"prop-types":"prop-types","react-redux":"react-redux"}],35:[function(require,module,exports){
-"use strict";function _interopRequireWildcard(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&(t[a]=e[a]);return t.default=e,t}function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function OptionMenu(e){var t=e.openOptions;return _react2.default.createElement("div",null,_react2.default.createElement(_HideInStatic2.default,null,_react2.default.createElement("div",{className:"menu-group"},_react2.default.createElement("div",{className:"menu-content"},_react2.default.createElement(_Button2.default,{title:"Open Options",icon:"fa-cogs text-primary",onClick:t},"Edit Options ",_react2.default.createElement("sup",null,"alpha"))),_react2.default.createElement("div",{className:"menu-legend"},"Options Editor")),_react2.default.createElement("div",{className:"menu-group"},_react2.default.createElement("div",{className:"menu-content"},_react2.default.createElement(_MenuToggle.SettingsToggle,{setting:"anticache"},"Strip cache headers ",_react2.default.createElement(_DocsLink2.default,{resource:"features/anticache.html"})),_react2.default.createElement(_MenuToggle.SettingsToggle,{setting:"showhost"},"Use host header for display"),_react2.default.createElement(_MenuToggle.SettingsToggle,{setting:"ssl_insecure"},"Verify server certificates")),_react2.default.createElement("div",{className:"menu-legend"},"Quick Options"))),_react2.default.createElement("div",{className:"menu-group"},_react2.default.createElement("div",{className:"menu-content"},_react2.default.createElement(_MenuToggle.EventlogToggle,null)),_react2.default.createElement("div",{className:"menu-legend"},"View Options")))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_MenuToggle=require("./MenuToggle"),_Button=require("../common/Button"),_Button2=_interopRequireDefault(_Button),_DocsLink=require("../common/DocsLink"),_DocsLink2=_interopRequireDefault(_DocsLink),_HideInStatic=require("../common/HideInStatic"),_HideInStatic2=_interopRequireDefault(_HideInStatic),_modal=require("../../ducks/ui/modal"),modalActions=_interopRequireWildcard(_modal);OptionMenu.title="Options",exports.default=(0,_reactRedux.connect)(null,{openOptions:function(){return modalActions.setActiveModal("OptionModal")}})(OptionMenu);
+"use strict";function _interopRequireWildcard(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&(t[a]=e[a]);return t.default=e,t}function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function OptionMenu(e){var t=e.openOptions;return _react2.default.createElement("div",null,_react2.default.createElement(_HideInStatic2.default,null,_react2.default.createElement("div",{className:"menu-group"},_react2.default.createElement("div",{className:"menu-content"},_react2.default.createElement(_Button2.default,{title:"Open Options",icon:"fa-cogs text-primary",onClick:t},"Edit Options ",_react2.default.createElement("sup",null,"alpha"))),_react2.default.createElement("div",{className:"menu-legend"},"Options Editor")),_react2.default.createElement("div",{className:"menu-group"},_react2.default.createElement("div",{className:"menu-content"},_react2.default.createElement(_MenuToggle.SettingsToggle,{setting:"anticache"},"Strip cache headers ",_react2.default.createElement(_DocsLink2.default,{resource:"features/anticache.html"})),_react2.default.createElement(_MenuToggle.SettingsToggle,{setting:"showhost"},"Use host header for display"),_react2.default.createElement(_MenuToggle.SettingsToggle,{setting:"ssl_insecure"},"Don't verify server certificates")),_react2.default.createElement("div",{className:"menu-legend"},"Quick Options"))),_react2.default.createElement("div",{className:"menu-group"},_react2.default.createElement("div",{className:"menu-content"},_react2.default.createElement(_MenuToggle.EventlogToggle,null)),_react2.default.createElement("div",{className:"menu-legend"},"View Options")))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_reactRedux=require("react-redux"),_MenuToggle=require("./MenuToggle"),_Button=require("../common/Button"),_Button2=_interopRequireDefault(_Button),_DocsLink=require("../common/DocsLink"),_DocsLink2=_interopRequireDefault(_DocsLink),_HideInStatic=require("../common/HideInStatic"),_HideInStatic2=_interopRequireDefault(_HideInStatic),_modal=require("../../ducks/ui/modal"),modalActions=_interopRequireWildcard(_modal);OptionMenu.title="Options",exports.default=(0,_reactRedux.connect)(null,{openOptions:function(){return modalActions.setActiveModal("OptionModal")}})(OptionMenu);
},{"../../ducks/ui/modal":64,"../common/Button":45,"../common/DocsLink":46,"../common/HideInStatic":49,"./MenuToggle":34,"react":"react","react-redux":"react-redux"}],36:[function(require,module,exports){
"use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function MainView(e){var t=e.hasSelection;return _react2.default.createElement("div",{className:"main-view"},_react2.default.createElement(_FlowTable2.default,null),t&&_react2.default.createElement(_Splitter2.default,{key:"splitter"}),t&&_react2.default.createElement(_FlowView2.default,{key:"flowDetails"}))}Object.defineProperty(exports,"__esModule",{value:!0});var _react=require("react"),_react2=_interopRequireDefault(_react),_propTypes=require("prop-types"),_propTypes2=_interopRequireDefault(_propTypes),_reactRedux=require("react-redux"),_Splitter=require("./common/Splitter"),_Splitter2=_interopRequireDefault(_Splitter),_FlowTable=require("./FlowTable"),_FlowTable2=_interopRequireDefault(_FlowTable),_FlowView=require("./FlowView"),_FlowView2=_interopRequireDefault(_FlowView);MainView.propTypes={hasSelection:_propTypes2.default.bool.isRequired},exports.default=(0,_reactRedux.connect)(function(e){return{hasSelection:!!e.flows.byId[e.flows.selected[0]]}},{})(MainView);
diff --git a/mitmproxy/tools/web/webaddons.py b/mitmproxy/tools/web/webaddons.py
index 6b52188c..bd85651a 100644
--- a/mitmproxy/tools/web/webaddons.py
+++ b/mitmproxy/tools/web/webaddons.py
@@ -1,3 +1,8 @@
+import webbrowser
+
+from mitmproxy import ctx
+
+
class WebAddon:
def load(self, loader):
loader.add_option(
@@ -13,6 +18,42 @@ class WebAddon:
"Web UI port."
)
loader.add_option(
- "web_iface", str, "127.0.0.1",
- "Web UI interface."
+ "web_host", str, "127.0.0.1",
+ "Web UI host."
)
+
+ def running(self):
+ if hasattr(ctx.options, "web_open_browser") and ctx.options.web_open_browser:
+ web_url = "http://{}:{}/".format(ctx.options.web_host, ctx.options.web_port)
+ success = open_browser(web_url)
+ if not success:
+ ctx.log.info(
+ "No web browser found. Please open a browser and point it to {}".format(web_url),
+ )
+
+
+def open_browser(url: str) -> bool:
+ """
+ Open a URL in a browser window.
+ In contrast to webbrowser.open, we limit the list of suitable browsers.
+ This gracefully degrades to a no-op on headless servers, where webbrowser.open
+ would otherwise open lynx.
+
+ Returns:
+ True, if a browser has been opened
+ False, if no suitable browser has been found.
+ """
+ browsers = (
+ "windows-default", "macosx",
+ "google-chrome", "chrome", "chromium", "chromium-browser",
+ "firefox", "opera", "safari",
+ )
+ for browser in browsers:
+ try:
+ b = webbrowser.get(browser)
+ except webbrowser.Error:
+ pass
+ else:
+ b.open(url)
+ return True
+ return False
diff --git a/mitmproxy/types.py b/mitmproxy/types.py
index ac992217..0d4ffd69 100644
--- a/mitmproxy/types.py
+++ b/mitmproxy/types.py
@@ -134,7 +134,7 @@ class _IntType(_BaseType):
try:
return int(s)
except ValueError as e:
- raise exceptions.TypeError from e
+ raise exceptions.TypeError(str(e)) from e
def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
return isinstance(val, int)
@@ -328,7 +328,7 @@ class _FlowType(_BaseFlowType):
try:
flows = manager.execute("view.flows.resolve %s" % (s))
except exceptions.CommandError as e:
- raise exceptions.TypeError from e
+ raise exceptions.TypeError(str(e)) from e
if len(flows) != 1:
raise exceptions.TypeError(
"Command requires one flow, specification matched %s." % len(flows)
@@ -347,7 +347,7 @@ class _FlowsType(_BaseFlowType):
try:
return manager.execute("view.flows.resolve %s" % (s))
except exceptions.CommandError as e:
- raise exceptions.TypeError from e
+ raise exceptions.TypeError(str(e)) from e
def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
try:
diff --git a/pathod/language/actions.py b/pathod/language/actions.py
index 3e48f40d..916cd0aa 100644
--- a/pathod/language/actions.py
+++ b/pathod/language/actions.py
@@ -6,7 +6,7 @@ import pyparsing as pp
from . import base
-@total_ordering
+@total_ordering # type: ignore
class _Action(base.Token):
"""
diff --git a/release/README.md b/release/README.md
index 1b8614ea..8632d644 100644
--- a/release/README.md
+++ b/release/README.md
@@ -3,6 +3,7 @@
These steps assume you are on the correct branch and have a git remote called `origin` that points to the `mitmproxy/mitmproxy` repo. If necessary, create a major version branch starting off the release tag (e.g. `git checkout -b v4.x v4.0.0`) first.
- Update CHANGELOG.
+- Verify that the compiled mitmweb assets are up-to-date.
- Verify that all CI tests pass.
- Verify that `mitmproxy/version.py` is correct. Remove `.dev` suffix if it exists.
- Tag the release and push to Github.
diff --git a/release/cibuild.py b/release/cibuild.py
index b2bad9a0..d070a4b9 100755
--- a/release/cibuild.py
+++ b/release/cibuild.py
@@ -454,10 +454,10 @@ def build_wininstaller(be: BuildEnviron): # pragma: no cover
return
click.echo("Building wininstaller package...")
- IB_VERSION = "19.10.0"
+ IB_VERSION = "20.3.0"
IB_DIR = pathlib.Path(be.release_dir) / "installbuilder"
IB_SETUP = IB_DIR / "setup" / f"{IB_VERSION}-installer.exe"
- IB_CLI = fr"C:\Program Files (x86)\BitRock InstallBuilder Enterprise {IB_VERSION}\bin\builder-cli.exe"
+ IB_CLI = fr"C:\Program Files (x86)\VMware InstallBuilder Enterprise {IB_VERSION}\bin\builder-cli.exe"
IB_LICENSE = IB_DIR / "license.xml"
if not os.path.isfile(IB_CLI):
@@ -470,11 +470,11 @@ def build_wininstaller(be: BuildEnviron): # pragma: no cover
click.secho(f"Downloading... {round(100 * done / total)}%")
urllib.request.urlretrieve(
- f"https://clients.bitrock.com/installbuilder/installbuilder-enterprise-{IB_VERSION}-windows-installer.exe",
+ f"https://installbuilder.com/installbuilder-enterprise-{IB_VERSION}-windows-installer.exe",
IB_SETUP.with_suffix(".tmp"),
reporthook=report
)
- shutil.move(IB_SETUP.with_suffix(".tmp"), IB_SETUP)
+ shutil.move(str(IB_SETUP.with_suffix(".tmp")), str(IB_SETUP))
click.echo("Install InstallBuilder...")
subprocess.run([str(IB_SETUP), "--mode", "unattended", "--unattendedmodeui", "none"], check=True)
diff --git a/release/docker/README.md b/release/docker/README.md
index b676d3ae..2fa93949 100644
--- a/release/docker/README.md
+++ b/release/docker/README.md
@@ -23,7 +23,7 @@ $ docker run --rm -it -p 8080:8080 mitmproxy/mitmproxy mitmdump
For `mitmweb`, you also need to expose port 8081:
```sh
# this makes :8081 accessible to the local machine only
-$ docker run --rm -it -p 8080:8080 -p 127.0.0.1:8081:8081 mitmproxy/mitmproxy mitmweb --web-iface 0.0.0.0
+$ docker run --rm -it -p 8080:8080 -p 127.0.0.1:8081:8081 mitmproxy/mitmproxy mitmweb --web-host 0.0.0.0
```
You can also pass options directly via the CLI:
diff --git a/release/hooks/hook-pkg_resources.py b/release/hooks/hook-pkg_resources.py
new file mode 100644
index 00000000..8bd787f2
--- /dev/null
+++ b/release/hooks/hook-pkg_resources.py
@@ -0,0 +1,7 @@
+# flake8: noqa
+
+# temporary fix for https://github.com/pypa/setuptools/issues/1963
+# can be removed when we upgrade to PyInstaller 3.7.
+hiddenimports = collect_submodules('pkg_resources._vendor')
+hiddenimports.append('pkg_resources.py2_warn')
+excludedimports = ['__main__']
diff --git a/setup.py b/setup.py
index 77b1098c..d2ff5f8c 100644
--- a/setup.py
+++ b/setup.py
@@ -66,25 +66,25 @@ setup(
"Brotli>=1.0,<1.1",
"certifi>=2019.9.11", # no semver here - this should always be on the last release!
"click>=7.0,<8",
- "cryptography>=2.1.4,<2.5",
+ "cryptography>=2.9,<3.0",
"flask>=1.1.1,<1.2",
- "h2>=3.0.1,<4",
+ "h2>=3.2.0,<4",
"hyperframe>=5.1.0,<6",
"kaitaistruct>=0.7,<0.9",
- "ldap3>=2.6.1,<2.7",
+ "ldap3>=2.6.1,<2.8",
"passlib>=1.6.5, <1.8",
- "protobuf>=3.6.0, <3.11",
+ "protobuf>=3.6.0, <3.12",
"pyasn1>=0.3.1,<0.5",
- "pyOpenSSL==19.0.0",
+ "pyOpenSSL>=19.1.0,<19.2",
"pyparsing>=2.4.2,<2.5",
- "pyperclip>=1.6.0,<1.8",
+ "pyperclip>=1.6.0,<1.9",
"ruamel.yaml>=0.16,<0.17",
"sortedcontainers>=2.1.0,<2.2",
"tornado>=4.3,<7",
"urwid>=2.1.0,<2.2",
- "wsproto>=0.14.0,<0.15.0",
+ "wsproto>=0.14,<0.16",
"publicsuffix2>=2.20190812,<3",
- "zstandard>=0.11.0,<0.13.0",
+ "zstandard>=0.11,<0.14",
],
extras_require={
':sys_platform == "win32"': [
diff --git a/test/mitmproxy/addons/test_script.py b/test/mitmproxy/addons/test_script.py
index 05472d9a..61e19174 100644
--- a/test/mitmproxy/addons/test_script.py
+++ b/test/mitmproxy/addons/test_script.py
@@ -137,6 +137,17 @@ class TestScript:
assert await tctx.master.await_log("error.py")
@pytest.mark.asyncio
+ async def test_optionexceptions(self, tdata):
+ with taddons.context() as tctx:
+ sc = script.Script(
+ tdata.path("mitmproxy/data/addonscripts/configure.py"),
+ True,
+ )
+ tctx.master.addons.add(sc)
+ tctx.configure(sc)
+ assert await tctx.master.await_log("Options Error")
+
+ @pytest.mark.asyncio
async def test_addon(self, tdata):
with taddons.context() as tctx:
sc = script.Script(
diff --git a/test/mitmproxy/data/addonscripts/configure.py b/test/mitmproxy/data/addonscripts/configure.py
new file mode 100644
index 00000000..6f6ac06a
--- /dev/null
+++ b/test/mitmproxy/data/addonscripts/configure.py
@@ -0,0 +1,21 @@
+import typing
+
+from mitmproxy import exceptions
+
+
+class OptionAddon:
+ def load(self, loader):
+ loader.add_option(
+ name = "optionaddon",
+ typespec = typing.Optional[int],
+ default = None,
+ help = "Option Addon",
+ )
+
+ def configure(self, updates):
+ raise exceptions.OptionsError("Options Error")
+
+addons = [
+ OptionAddon()
+]
+
diff --git a/test/mitmproxy/data/servercert/9da13359.0 b/test/mitmproxy/data/servercert/9da13359.0
index 5868a304..88ed4145 100644
--- a/test/mitmproxy/data/servercert/9da13359.0
+++ b/test/mitmproxy/data/servercert/9da13359.0
@@ -1,21 +1,21 @@
------BEGIN CERTIFICATE-----
-MIIDXTCCAkWgAwIBAgIJALzkvKyFAwWYMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
-BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
-aWRnaXRzIFB0eSBMdGQwHhcNMTgwOTA3MDgyMjUxWhcNMzgwOTAyMDgyMjUxWjBF
-MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
-ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEAkvT4Y1ML8Gg4x5aFVygIW022tJsEyfuW4HsEvIarAGpFtUNkw0dOoAxv
-Gv71I0KAWOXxtc9DUjtq7ZXJcG+dBiheTYJ40lrYhHvUt7J37nrUF5v5trQzoE9I
-6WGtzTV8C8RI6F+M6GtwxgwRBgqkuXiK5ExPXAjGMMJZWtiEXHxGTNB6F4zy884m
-VjVtQi8jy+c5g21Awp3z5HrQLM210zwvgi7Rygk6/UM0AnmST4o32SqFSd+0MFUJ
-f3pH3xczfKmhU/TBoVEWRB1YzwixsJrzDOB8wOGnNKCsl45hYUJZZ8epVkyrvFZQ
-iMkwIqqBJbkU6H7fZBl68TJ8ascUCQIDAQABo1AwTjAdBgNVHQ4EFgQUkurgHlw1
-xMP2wrsrGPTk0ofxCyowHwYDVR0jBBgwFoAUkurgHlw1xMP2wrsrGPTk0ofxCyow
-DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcdExVlSvH6aVExNiQO3k
-cMamj+78woDn9x563vwzaGP24KvOXk1B/IJp5kqu3ZsXS0I0Mz6xwXHAXeuxaj06
-cKgEpHKKgClLblXo2zWqo/3V1UFFpOVP/NhI3r21b+fPrS46rP0mw75haQCph8/8
-buQr0OeAYbElliY/ji+cJiCJB8A/D13fUMV/NUUfPW/UE6497jOmz+6PtZNAoOFx
-evrmDcbCzbJxacyLJX04rsrt6DO09jb/+5lFm5Aqr6ySKasrmheIGEisl4o9Zbuy
-5PvYgbOEmFgPATIiWGpBO/rqwDdsmgyYFl+YfFoW0akXUVhDb2e5iRDx6Rs0fmN/
-NA==
------END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDazCCAlOgAwIBAgIUTE2oFYATbkkytGOt0+CIAOPpaeEwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTExMjMwMDA1MDlaFw0zOTEx
+MTgwMDA1MDlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDZ7fqv4iC6738OvOC/ONFmhOVmMNe2SrGtk+7KIv/M
+d7MJJKpIygwc99EfL3m6WFjvSQ8CABO9GnLIeoB6RzX5X904dPQzIekMkE27J4D5
+wzYWAmYsu9md//9rjadaA01zHvxtpc30AsToOrRa2Rv7Lwotvcop2bBCXXZMdgvD
+BpCuCOl3cQ346LZBmED41q77P7SgnI98gCPuyyMVAFTZE4NnFUloVERTcMMo5Vwv
+tXFT41B6FOqtyhZFY89W1SeNKlm+n8zu+/aUF5c9usxwvQzj79pzBFYS9xMZzbxl
+BmU91GbLFNU32O+wp0jX6c1MfluGvRjcHQpK+gHRRJdZAgMBAAGjUzBRMB0GA1Ud
+DgQWBBSTmtfFP/zQDiCbte3AG3vchjSekjAfBgNVHSMEGDAWgBSTmtfFP/zQDiCb
+te3AG3vchjSekjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB6
+CnGmyJKVdD41Qs8QbyZ5VvrAkSq7DkL4CAIDy0pClOYjx51R16g4/dXPd8nuDjVB
+GbRuO/ZHlKeO+RAVbV4s74KeBmQ4uyuZMvuiJGCDtTgu+fbgp1l41vj21bkDnz/X
+0aVrwC4clh6O1/AFCY7cYygp22QJ0zGpIb9d0ly6QDMEocHJKasuQ8/0GPvv6wkY
+hwNub2ScsVFHPrFzzufZblBk3Ks0YZw+IsCMTY7QtGb1TZhGeX9xAydqNMnmIxNj
+XOE2FKx4ISQNFwf/U1LEAruO7PjbbsWx5FHr4oYV9TCLoERZFofwcGGM9o/cPc4P
+CH8fGvFYoU30PG+xX0Pc
+-----END CERTIFICATE-----
diff --git a/test/mitmproxy/data/servercert/self-signed.pem b/test/mitmproxy/data/servercert/self-signed.pem
index d35284bd..55145c52 100644
--- a/test/mitmproxy/data/servercert/self-signed.pem
+++ b/test/mitmproxy/data/servercert/self-signed.pem
@@ -1,46 +1,49 @@
-----BEGIN CERTIFICATE-----
-MIIDEzCCAfugAwIBAgIJAKzH8k6aKTP6MA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNV
-BAMMFWV4YW1wbGUubWl0bXByb3h5Lm9yZzAeFw0xODA5MDcwODIyNTNaFw0zODA5
-MDIwODIyNTNaMCAxHjAcBgNVBAMMFWV4YW1wbGUubWl0bXByb3h5Lm9yZzCCASIw
-DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOjJMp2o5eLQEmYJqMZzLBi61h9
-fsCVMvS8hgrH1Cg5q/RaLBLrZ8nILKmFZBapMUEFkUwQLB864tdTMaX7p+jNv3sM
-5LWEIYkTIbu6qV7QerKdubS1hpdFtQGRM1Q+C7H86FzF02DSKzNSmQc4fNed/lQM
-qo/jOm1xx4TZFR4j58BrmmoOfNP44IyrwXsPyXbMsukKixVEB3vQ2oyGDAyG6dYi
-VvM8PVL5yhX3BJ0D1Ky6hgGHJeirm0Cd8qqdSC/SWNdu1bGzg/xyUX5XFaHlIi7Y
-5YhD7ZDLvC76MeCWkfo4DaSB0CWmtG4l1TtHM2JqP8qf2l2LsABKs0q/a+UCAwEA
-AaNQME4wHQYDVR0OBBYEFIc9YAXgnGRhPTEcN/j+k/dxMdKqMB8GA1UdIwQYMBaA
-FIc9YAXgnGRhPTEcN/j+k/dxMdKqMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
-BQADggEBAD9qKci3Pr4/2WGx+sv8gOpKchC9eF2dXc5hA3xbDw7T6oRLUBAY8Pty
-JF7DHMovT+w7FPRYT8rSc190fbSwVRHAnEaqAzaxteImCp/qYgdBHOz39eG4c93W
-YrYvA1VdUDPcUnisEVWguDsKJGFg+G6pw+8Wkf/hCrJJkriTFogGvzg6ptdQatvE
-dpSkionfbuZKz+7lny6sCBGoMRIFBd22MHJsSQOyTb06Lwc5dpdF9c5vysPRzShJ
-5kkgIjGpTmWp+Ud8BAMQH8EDhJMkJ7iw1+07UQ9MUmXCp9Xgim6x1ri2/yoz9HeO
-83VCkD9YWufrzOrsXpo04rMYtoKo+lw=
+MIIDjTCCAnWgAwIBAgIUb0mIVHKB+bu2PGObggokU3Re7xMwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTExMjMwMDA1MDlaFw0zOTEx
+MTgwMDA1MDlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDF6eqyZD2z83buUF4T+6AY0Zoe925a2AHOhGtHJMLo
+9AD7FF1Xi1iksEvxbOI6mreHtvYKzUpfNsA3DkFdSO91HMSkdvWcDcExpW62sNK9
+gQQrpcCx7DogOrFiGSIHI1LIy1y6YEJma3G71SgGbw7g1QF64dTX9+BzVQJsloT2
+H3ZxTi8Fb6APJq6d/Tp67GTM8U82vM+FjLKzfH7RMSEPvSyvWSibw3AZP6owFaxz
+DJtXpR7evOZbiZxqXmGOBl5OQPu9GdDA3Fyi9Drp7xa234loqd1a8PYyL8qWV/2G
+HM3IJOzG8Y5PUJhL8CkDbx4LJ9LfzeSuBnQPUf2ZNalZAgMBAAGjdTBzMB0GA1Ud
+DgQWBBQbkS0mAGPD4XjOv9GKzGMyR9ix3TAfBgNVHSMEGDAWgBQbkS0mAGPD4XjO
+v9GKzGMyR9ix3TAPBgNVHRMBAf8EBTADAQH/MCAGA1UdEQQZMBeCFWV4YW1wbGUu
+bWl0bXByb3h5Lm9yZzANBgkqhkiG9w0BAQsFAAOCAQEAqZ64xpu3qrTlCc555OoP
+GgobUFP3qv07d0/r48cOyYdAdlEHhvmDPlqWTB9e4ZYtWZMlocY9DpCywzKTa7F7
+Ad8BwS0a/No3wVdl1UEkIGYxuD//jbd77Mrpf5URvQco85o/bicn+H0GAOchYt1P
+jP1VShqsRv6WiTs5kn1/JwVoafddl1jBlMDmCqDv4loAZJYHzie0CqdjjSeorFfE
+8FG8OLwmEnmIW6VnanRH8coH9MBbZ+dRtCavS+Q8s0R77dJM1sCp5/4yKcr5D/PD
++dQN9f+iugLxDdBQjiRyadWX9/l4n/h8ezabZ4cNsiWbRXrp5VS0nGNmAK3XvPAu
+ow==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
-MIIEpgIBAAKCAQEAw6Mkynajl4tASZgmoxnMsGLrWH1+wJUy9LyGCsfUKDmr9Fos
-EutnycgsqYVkFqkxQQWRTBAsHzri11Mxpfun6M2/ewzktYQhiRMhu7qpXtB6sp25
-tLWGl0W1AZEzVD4LsfzoXMXTYNIrM1KZBzh8153+VAyqj+M6bXHHhNkVHiPnwGua
-ag580/jgjKvBew/Jdsyy6QqLFUQHe9DajIYMDIbp1iJW8zw9UvnKFfcEnQPUrLqG
-AYcl6KubQJ3yqp1IL9JY127VsbOD/HJRflcVoeUiLtjliEPtkMu8Lvox4JaR+jgN
-pIHQJaa0biXVO0czYmo/yp/aXYuwAEqzSr9r5QIDAQABAoIBAQC1RpgymlffdgJt
-rvQuMRu/XQlhh3dJj3YV3BIAL0VguH+i/WLVbRdQm5D2y0kAzml7LGODrYCUt4W1
-q7rXaCYfy3Xf2QSbRQGl9/pL7xw9ZMQseYW38nPx+39LInYDWzKPDB9qx0uj7Vpm
-ReTSEf9r81PUIaBxj0V2X/VWHag5scBjXoflQLxyV6i1UvTuWyhYvX1Bbaj02MqV
-tGNMrjbj25Wx2za53VDonzNA6RMZzkWGMfzmkkGM6kjGzLEsveys+bYCt7Fs7slR
-4oby0bIUmN7iqLhqlEhS4weWW4iHlq17X7CZeQAE1XeVZBz1N4G8FLjND2eyqb2N
-RAcQqp3BAoGBAOiU3WTu/kSccteDRxR1gVRPqEgfoLDwyAb7ORVUWX4Ii/z/soMw
-xZ2MlYPLnp3Fyu/hKhJPC1LzkD4CGHCTJJ1NnUudtDxl2Zh1FZYGmv1hi1TID/cm
-G0+3XhlJgztS41+AzxTNMulV1yieT2HIRIoRpdSx1UIA72l42YqjrwUNAoGBANdV
-/Ib+3hAfFtSMMI1qZQXvlKEoDRbUOCYuBVkTK8oQJQH6MLDokHZ8sXBAqi9383b1
-XmhQBJZ//yMy0AqFa2QBlkK0Gizzhh7BLSjIT2LREf66B2cWzhgdhbSp6Nuk+3DK
-NfibxsFAPpW05HqtfxhbjrLfoE8VvTuMGQ8AaXw5AoGBAISo7IL2wrdV2TdN3Mwx
-ndv+N4kz6Q8jt6QrxUqCOy1lKJvdKPAlcIJFvr5W9RkeyXr7nmilB1uAK4UC4vfL
-JfZHX/HSeQx+N5f7KJ3TFLJz4eow1tJsvOVCPP0FbkH3LFO7/+HojSKEYN39NmAa
-v+VU3Zas/GvSZrxtPwASDvE9AoGBAJOBbluW6MzITx5H7dZhRFR9miWOxvCVbOUS
-b01mKX/f8UnadVIp7RONNQr88NdVZqxdRk9USOBDS6Vz4DjkzfySbbjBoJCcPIqC
-r4mZNXAuYRJJolqGr6SrTHTGUyFqcWcAzVnAc7TbakOoxz4V7NLlnOmA8FJcROUu
-gdfZ42hZAoGBANyL2IQ+L92iYVvqsz1zPBlvemevx8zP6GmlzvTcVexyDTIiLg6W
-BVil5zRDPJdDiFfBK18Qg1mJoE4SjLTg+yGww9ef37Zb9kZypy6pM6AbRWILZ1Gv
-7UsWUzk6rgcQpDdpJCUEt+AD3LQJTxxuoIhZePvC2GLkzsjZA7ZyB5+S
+MIIEpAIBAAKCAQEAxenqsmQ9s/N27lBeE/ugGNGaHvduWtgBzoRrRyTC6PQA+xRd
+V4tYpLBL8WziOpq3h7b2Cs1KXzbANw5BXUjvdRzEpHb1nA3BMaVutrDSvYEEK6XA
+sew6IDqxYhkiByNSyMtcumBCZmtxu9UoBm8O4NUBeuHU1/fgc1UCbJaE9h92cU4v
+BW+gDyaunf06euxkzPFPNrzPhYyys3x+0TEhD70sr1kom8NwGT+qMBWscwybV6Ue
+3rzmW4mcal5hjgZeTkD7vRnQwNxcovQ66e8Wtt+JaKndWvD2Mi/Kllf9hhzNyCTs
+xvGOT1CYS/ApA28eCyfS383krgZ0D1H9mTWpWQIDAQABAoIBAGl4H8+TZeJ5E18q
+ywfhJ08ym+x2tYOJ62SP4s+WApy8M62aC6g0pTeWj9IH0YOjobycPwBAqKqW9dYh
+Lao1zQ5fF1gB4R+ZoOQBIkAPeS7uCzfrbAYlOlCklpUNibm+FEbXQQI9fAUyqviL
+Pno3QvmD6fb/VDsHaMBthA40JIU4C3yUUcSooKKUC3XKmbRDg5/stKbVdi6co5AQ
+OMNFj4smts3rXtk5tKMBep4AgKKdClf4IuhpWEqZDxN6e+hZA7g1VOmOIE+hKEEO
+ODBkjHNgHIFbsr8GTlZ+GSkHBxMNVdHxntJTU5a2jPiTNTTnhqu2hcVdkyDwLb9x
+TGUCdyUCgYEA/ZPwI2KaT4Lj8b+6DCV4v3nZF8nLCtt1LiXxJWexdnr+3q/ZfNjN
+5sKVl9rIynz2zoJDVr1MI8yjO6OmV7GBW2wgJsSFwg0vnV/bs75KqPZWZXqy/mrp
+HnXd82XUweyyOpmBg4cCjmcnMTrdvCHvfgx7JafnUDqeXNwriWeC5Y8CgYEAx83d
+qHgAT2rlnCQYRmCOGb5fuyQ+pSM4p8h1AjueLrTNiWcqX57ct9eVWjymD+AgevFZ
+hZH9m6TI4nCb/5ukcvjQb4g1kyY3l7rZiiJ3kf/c22kVsVUagJGeiW6Ypnox4r1o
+oFXEwAw1qSGIqOIjYVB2DqvjUl3+UjWCl9SOnpcCgYEA0TSdWUQ/VTwCvW9VmjHM
+FgT8I5Ebj+CRI7qv4hFTqxE8dxKTl1nzPd/ptTgOkmhY4vU7gzN3vs1VGp4gXZcX
+xwpE2Fcol3lzgB4Wz4s+Y3mgu+ZoCFjB7ZyGugmYZ0nVnV0KKi5X4I6gGhCb4VwK
+D29SpjWJNHq4LpqC3MDmkGcCgYAxnKOKXmmtTpy+3ZONfhIqwEOjA0fu10UNHFA5
+grYvYMOcd5pk7dxeZdB2/JI7ZOqLvHv/F5YCXLNozo9ds7bsuW2AFDFBXX72VPYJ
+P6+y9/ZOINS7GKeg/wd/lo+e3r6eT2u4TDOzgBSe722wiZ5BXqpB0Fp8rEwm+5R2
+wNe89wKBgQC59SOa4EW3gfjLRaH6hEPcMEzjr0Dt5ilBgntYbI+DsCePwEX28hAK
+fd7A6XoCqjnwLpAmh0cmNRsYjtPCzg6TXUdrhfnR/3r7DHzvsxaC06BfkPZJ0+Fu
+rP3el/oKSPaDsZKjW6RA5oqlIF82o45MNl2oCG82LgVtu25e3HbJSA==
-----END RSA PRIVATE KEY-----
diff --git a/test/mitmproxy/data/servercert/trusted-leaf.pem b/test/mitmproxy/data/servercert/trusted-leaf.pem
index a2c25312..29bd38f7 100644
--- a/test/mitmproxy/data/servercert/trusted-leaf.pem
+++ b/test/mitmproxy/data/servercert/trusted-leaf.pem
@@ -1,45 +1,48 @@
-----BEGIN CERTIFICATE-----
-MIIC4TCCAckCCQCj6D9oVylb9zANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
-VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
-cyBQdHkgTHRkMB4XDTE4MDkwNzA4MjI1MloXDTM4MDkwMjA4MjI1MlowIDEeMBwG
-A1UEAwwVZXhhbXBsZS5taXRtcHJveHkub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOC
-AQ8AMIIBCgKCAQEAqKzVdsRKgthv6V/dk3Tncy4ymbACs383nGutjulExvroNOCw
-b0y0e7unNGbtXxFQqSvA7eGaT1yRNfoMbXGSS+sn8A3gB6/s2A0Sw7KeSDdoaqEq
-F/LzRBed1YkxSyy0GXuTd7HXNIoFn/eF1tqxgViWdfyFD85qY4yJ+luofdm7IcPM
-ENPzV4nKzDh2PdJpQrEokWz2jM0zefC3IYnFHXY5bA3MnhE03/P0VxeEYkBdmEAt
-O1U2Bkw9SKCLy9zF13ks6/dDZ9LjMtRKI83gQS5z3S3bA45YxFuyeLWgVsJ2NYTa
-j9/8c4xwOjg9TpkCvcmZiPUYGddPHWoKqAAhBwIDAQABMA0GCSqGSIb3DQEBCwUA
-A4IBAQAf8cjxunN4Y7NUD2Z/SNOJ/s0uWJtTPV6m4FxSwwD0wfbsyirPchmattLc
-BabrQkeMMm8gMOrORfanXQwvLZvX0aDf96EgLSfHv8Iqeol5Byrgkn7UORXl20Jt
-8UNRURUZYtWxn08P8dlhxQUncPF/UxCesC8x0cihqv+YTB3TX1sni9mOqPCYY8yH
-E8kCW4zTJ0J9OQUHq9qdYQM/PGVm99+DWBItUeZAva8Rqj1FN3f9j1eWB+EjfYu7
-ztsTInpNWP4tIh6vIFtuaGr077cJawTe6YVyNxVqquI9+2fpSPkt7tCTIhbQ4AmM
-DeHzn+KjfKN8ooWqmcfmUZWaADe0
+MIIDajCCAlKgAwIBAgIJAKPoP2hXKVv4MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTkxMTIzMDAwNTA5WhcNMzkxMTE4MDAwNTA5WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA4BZUGKkf52583Kps2SinCzRtGFaGIqtP1YfjaN1H21c08hEERRWf0GqW
+PIbZz0NRyqOVM0O+4A9cxKrdqDpALBNXdgB62Ven1NNE31ZjsbFgwbORPTiOQo+6
+Xsu+2KHQdqAGOXSqOdNSoZV/HqAKa41iweg6lUf3yrO/hpfnlXTFbuM/oYFARrr9
+YWY7qH5BEvcZBf4Sg3cWGjdbzYg8S6hnFBBkHPDMbS3xra7H8uru8aG9L70aiq0C
+3WsGf1/Qctz3cccB6BdnbiXji0QlP0miT+b52hEzDvNjdXvoObgbOZsUUcLrbz8q
+LqaQPj6TA2PYpKcxEdJLDcbN++tglQIDAQABo10wWzAfBgNVHSMEGDAWgBSTmtfF
+P/zQDiCbte3AG3vchjSekjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDAgBgNVHREE
+GTAXghVleGFtcGxlLm1pdG1wcm94eS5vcmcwDQYJKoZIhvcNAQELBQADggEBAMoK
+8+KTYF10dbQ8Hl3Fq4Iux7eutAPuKUXUz9dnCfDRhZukdl+gahRoNvkyMBdXOvvx
+R9Z2+Guo8NOYKgT1mJtS/c8IxTkjZj9doujJOVD2wTowSfoaT9z+/EJa+6Fp9+xd
+YVsO3E/2Vxai8PCNx8JTXr2axcnBDvpHPRXF21hOI8N94SPAcmLTZsdsTELjrGGa
+/BA0y+pCEwW6cY9mMHVAAvRoMoqfocBVI7nrYBaQfFoKuwxscxO679eEv+lbSjul
+z/VNdWfqrDhFFSzwRSVchapQ9q1EeTzv++wZRwI5bT0Ib6DFTJB3J5+9RihlFYfU
+GI17CI0D//DsFic7QJ0=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEAqKzVdsRKgthv6V/dk3Tncy4ymbACs383nGutjulExvroNOCw
-b0y0e7unNGbtXxFQqSvA7eGaT1yRNfoMbXGSS+sn8A3gB6/s2A0Sw7KeSDdoaqEq
-F/LzRBed1YkxSyy0GXuTd7HXNIoFn/eF1tqxgViWdfyFD85qY4yJ+luofdm7IcPM
-ENPzV4nKzDh2PdJpQrEokWz2jM0zefC3IYnFHXY5bA3MnhE03/P0VxeEYkBdmEAt
-O1U2Bkw9SKCLy9zF13ks6/dDZ9LjMtRKI83gQS5z3S3bA45YxFuyeLWgVsJ2NYTa
-j9/8c4xwOjg9TpkCvcmZiPUYGddPHWoKqAAhBwIDAQABAoIBABsUC/zSDEgvKOAl
-RLP8a3+hJfxoNjbMsIfK/YTYy/LJqud6PrjPbpYCjRgrgeXmKLXP0VwfAJ/G84Tf
-zIjxV5Qaf0HZaGKzimkwyBdkoGZlhry/fLt1hDolNHBoYuJ3nb4NiaIIiczkb3y7
-xt+0IhTqvNTaIh5ke83ZbPklJ8p0HAzw1q6+iRFZiZKH1iVRwJyIyK654wpNQ93W
-SqUKa3uAbqw+Bx9fzyEunANFwoBcZka9oSR9bTlhGB8HPHZFVYKgZvE4n9WOclKW
-E75pGG6vFYZkxBdqcjFNlPKKZRisDuey28teiHXThh1MvYxRdaMq4oxOE1J+n17k
-F2gQolECgYEA01j1FlExu45+U36n3tCCyS50dTtf0Qpi71c1s5DZyT+AAB2ZSGXm
-VBtKgVRNg/iWfHn5b/zHF30OtgIzcsrU66cWMwIXPUQigXh8Cteve7VMs03hce1w
-wsFwLoyvdWEam32YAymqRgN3H6JQim82IJJ3YlWrgEytBnvLkADCihkCgYEAzE/g
-8aoxDZJUwbvaZjLwuydmvc+aAwanVgqvtkca4x99oPhNQna6O0jXXAtJXMA3SHp5
-QYMDKh98BqCXyfXd+1Semc9pgAPz7l4j09WG7Zdap3xinTOkUsmTAz/2T967HIsP
-6qP7RUiwjmbUk8ZGcKsNjoxzPA4JURYimKB5qB8CgYBTjlnnJtaYpi8/Z1WK+7iZ
-PSqBpqWtCYQvx7TNdzkDHX3Hjewp+U9kdR2xn9i9kiw8riR1p+Q2XxTP1HLusU4Y
-lIhsRilV6XgS48V2q+sO55CZWvMEjbEE7mEhpjFAINHaI39T0Mcmwvv3n75j3K/z
-lLRqRiB1qtrFM3A5UHOZEQKBgBPQm2xUqTU7v+SaJ3BJ+HbuN1SpUbKBbrE1kB0J
-gF4Oq8x0yGltwloFkn1mytKoAbSRzDjCUAhBzXGHGbGImuLJLiiUqRK1T28Kyka9
-KrzYNP6RXa8JVyKAUjW6elT8sQDvq7eB99icWCM3bd53GFXNAR+WF4b3hYfLscdD
-qQjZAoGAB3ah068Qscb2Ef3+eufa+EvOfMDrNNvlEoZXRlhviTg3NEwjyqbxaCIy
-6Xg+rWvgJm9UE2RBOcCNeghkEabmL1+8DvmDiV1lt9oJtqULBirvalp2H3+9yiTk
-j5dnVcRF6cYvNFmwTr2WZFBGgq96d/Zmbx3o3MIqSBc8I6pLNzo=
+MIIEowIBAAKCAQEA4BZUGKkf52583Kps2SinCzRtGFaGIqtP1YfjaN1H21c08hEE
+RRWf0GqWPIbZz0NRyqOVM0O+4A9cxKrdqDpALBNXdgB62Ven1NNE31ZjsbFgwbOR
+PTiOQo+6Xsu+2KHQdqAGOXSqOdNSoZV/HqAKa41iweg6lUf3yrO/hpfnlXTFbuM/
+oYFARrr9YWY7qH5BEvcZBf4Sg3cWGjdbzYg8S6hnFBBkHPDMbS3xra7H8uru8aG9
+L70aiq0C3WsGf1/Qctz3cccB6BdnbiXji0QlP0miT+b52hEzDvNjdXvoObgbOZsU
+UcLrbz8qLqaQPj6TA2PYpKcxEdJLDcbN++tglQIDAQABAoIBAG5V8DB4TdI5T9ej
+Ppcqch2NQc5DBCbb7SI5l5qRogj5BoPOJykQ/bC0WqcQyvxHrGU3aIZma/yM8+OO
+Mjfb/q71ExJyKAsOIwAiyn2hXtMmgHq/vNrFFx7lACIe9ihafHd8UbRGom54g+41
+2vKsYJUWd7L8cqQAXJz9JmfSMeAfRBVVLyOpENSVT8gM/LgPokmZbg0wRJTA5oa3
+10f4Ax1HqXO3cQgKKnrvIU72gB7MQ9Y2G+P2eAmiAlzGr41N5sv7uFz0JrFHhqtC
+qc7QrGVVNtxanu0s/LWgfQ+LrKldTysX16j/j6J0tg7Pfuq9cMFPklU98dv2LF5J
+jMB8eQECgYEA84+Yy/IGVjUPVb3HjmCmWUoxt4Aqop/12/4+1cBNS6h58iLJpcWc
+FVh8m3UbuE7shimEg0fZ1rPPkqepKlDw8/sKXDni/khBASOAh2v1RhvOxNGjxDfn
+ZvcK+BG99kU8ZWQHpKmn8OGfYswL2Fvo67L9XFg/wDnk1QpJx7E1GVUCgYEA64gg
+G6qnsah1NKSW64BPVKi/Gby83l4oa1amjHqq/Unq6iuGB4/QhK4BzW0aOGI7RMTE
+SPh5NF5ZhfT3LWD/EnaMm6QxMTSsL9TDIg36WTVZTkbODFOJczg19EdTs1pa6OXW
+0NM6psLu6Nf/QmHJzyYxSZ83uqPOM305xgooKkECgYEAnA0XMySglr9sUd1EbJ7U
+NkVpUU8XAhdHKWrey4lofN83MsLDPCk+dha5z8jat94pgVQ8iPiSRBP1HNu7cVdm
+6ouf+bNFEvMsYxRiF2I+RmsuscA4E1JWOwxxxLtpYM6/gZ7znrbs2VNWEbD2retF
+cy69UltgjUMKsMzktMN/Z/kCgYAbzV285lAVMIVlSWhnNCYpICIur5C7zvGGehv+
+yRwV+fu42JphmiBLCR89WHuX3ECSxYdF9c6Y1+pJXbkvqhtx2nyOgrsry8PngX3n
+Ly82CI4aJ1F7MwEukJwN0b2XljrU8wyAae6qcKgy5AxFkbV4tlFrF1hEt8FHYqjH
+L7u+AQKBgBnN2vjOVagtlZjJadg5ueR7uKnfk7/TGMtZjNf89C1UZH3TqWnnj6p6
+rLLe4tnA3EebFtGeVWlmnw5k166HRBEd7KuebOCq3hkNlf71aHwhDJ0bIdDwo1g+
+FTpG2vqkqpuEb+2FzDjZT5EdPnj8tUGthREDXpXqsdjFb6+2Enjx
-----END RSA PRIVATE KEY-----
diff --git a/test/mitmproxy/data/servercert/trusted-root.pem b/test/mitmproxy/data/servercert/trusted-root.pem
index a53cb893..2248d9f3 100644
--- a/test/mitmproxy/data/servercert/trusted-root.pem
+++ b/test/mitmproxy/data/servercert/trusted-root.pem
@@ -1,48 +1,48 @@
-----BEGIN CERTIFICATE-----
-MIIDXTCCAkWgAwIBAgIJALzkvKyFAwWYMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
-BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
-aWRnaXRzIFB0eSBMdGQwHhcNMTgwOTA3MDgyMjUxWhcNMzgwOTAyMDgyMjUxWjBF
-MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
-ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEAkvT4Y1ML8Gg4x5aFVygIW022tJsEyfuW4HsEvIarAGpFtUNkw0dOoAxv
-Gv71I0KAWOXxtc9DUjtq7ZXJcG+dBiheTYJ40lrYhHvUt7J37nrUF5v5trQzoE9I
-6WGtzTV8C8RI6F+M6GtwxgwRBgqkuXiK5ExPXAjGMMJZWtiEXHxGTNB6F4zy884m
-VjVtQi8jy+c5g21Awp3z5HrQLM210zwvgi7Rygk6/UM0AnmST4o32SqFSd+0MFUJ
-f3pH3xczfKmhU/TBoVEWRB1YzwixsJrzDOB8wOGnNKCsl45hYUJZZ8epVkyrvFZQ
-iMkwIqqBJbkU6H7fZBl68TJ8ascUCQIDAQABo1AwTjAdBgNVHQ4EFgQUkurgHlw1
-xMP2wrsrGPTk0ofxCyowHwYDVR0jBBgwFoAUkurgHlw1xMP2wrsrGPTk0ofxCyow
-DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcdExVlSvH6aVExNiQO3k
-cMamj+78woDn9x563vwzaGP24KvOXk1B/IJp5kqu3ZsXS0I0Mz6xwXHAXeuxaj06
-cKgEpHKKgClLblXo2zWqo/3V1UFFpOVP/NhI3r21b+fPrS46rP0mw75haQCph8/8
-buQr0OeAYbElliY/ji+cJiCJB8A/D13fUMV/NUUfPW/UE6497jOmz+6PtZNAoOFx
-evrmDcbCzbJxacyLJX04rsrt6DO09jb/+5lFm5Aqr6ySKasrmheIGEisl4o9Zbuy
-5PvYgbOEmFgPATIiWGpBO/rqwDdsmgyYFl+YfFoW0akXUVhDb2e5iRDx6Rs0fmN/
-NA==
+MIIDazCCAlOgAwIBAgIUTE2oFYATbkkytGOt0+CIAOPpaeEwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTExMjMwMDA1MDlaFw0zOTEx
+MTgwMDA1MDlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDZ7fqv4iC6738OvOC/ONFmhOVmMNe2SrGtk+7KIv/M
+d7MJJKpIygwc99EfL3m6WFjvSQ8CABO9GnLIeoB6RzX5X904dPQzIekMkE27J4D5
+wzYWAmYsu9md//9rjadaA01zHvxtpc30AsToOrRa2Rv7Lwotvcop2bBCXXZMdgvD
+BpCuCOl3cQ346LZBmED41q77P7SgnI98gCPuyyMVAFTZE4NnFUloVERTcMMo5Vwv
+tXFT41B6FOqtyhZFY89W1SeNKlm+n8zu+/aUF5c9usxwvQzj79pzBFYS9xMZzbxl
+BmU91GbLFNU32O+wp0jX6c1MfluGvRjcHQpK+gHRRJdZAgMBAAGjUzBRMB0GA1Ud
+DgQWBBSTmtfFP/zQDiCbte3AG3vchjSekjAfBgNVHSMEGDAWgBSTmtfFP/zQDiCb
+te3AG3vchjSekjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB6
+CnGmyJKVdD41Qs8QbyZ5VvrAkSq7DkL4CAIDy0pClOYjx51R16g4/dXPd8nuDjVB
+GbRuO/ZHlKeO+RAVbV4s74KeBmQ4uyuZMvuiJGCDtTgu+fbgp1l41vj21bkDnz/X
+0aVrwC4clh6O1/AFCY7cYygp22QJ0zGpIb9d0ly6QDMEocHJKasuQ8/0GPvv6wkY
+hwNub2ScsVFHPrFzzufZblBk3Ks0YZw+IsCMTY7QtGb1TZhGeX9xAydqNMnmIxNj
+XOE2FKx4ISQNFwf/U1LEAruO7PjbbsWx5FHr4oYV9TCLoERZFofwcGGM9o/cPc4P
+CH8fGvFYoU30PG+xX0Pc
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEAkvT4Y1ML8Gg4x5aFVygIW022tJsEyfuW4HsEvIarAGpFtUNk
-w0dOoAxvGv71I0KAWOXxtc9DUjtq7ZXJcG+dBiheTYJ40lrYhHvUt7J37nrUF5v5
-trQzoE9I6WGtzTV8C8RI6F+M6GtwxgwRBgqkuXiK5ExPXAjGMMJZWtiEXHxGTNB6
-F4zy884mVjVtQi8jy+c5g21Awp3z5HrQLM210zwvgi7Rygk6/UM0AnmST4o32SqF
-Sd+0MFUJf3pH3xczfKmhU/TBoVEWRB1YzwixsJrzDOB8wOGnNKCsl45hYUJZZ8ep
-VkyrvFZQiMkwIqqBJbkU6H7fZBl68TJ8ascUCQIDAQABAoIBAC/1mopvs9nFaaJZ
-UTLccb26YwIWBT4VyWuBOk58dJoyFIXPdLb2MoaxCCF7S20yasiYYoW/Gm1fzsmy
-tIbpJgm4au5Iwj2EQF0cPJOmvtUpaMY7tQcXUDHlLhpcMmhiKBV+/Xw4krfXOHqp
-vXSHTLLq0Akpjkyu4F9RTfAD8U5tEbpPsCGcsSJEHxPgqDexITDwB/yuhvrKKUwY
-t8WQBWO5M8D6Z1HGTFovIa86eX4hUKKbNB8sE7yi1wGxbOloIOQESOcqiisP5GGN
-d6r5k9jBwZXlyh7GR+GNILF+n6ctdOFr6MQQEKvDzjh/IVYADen19909Ed7Wn0gR
-C0Ec2gECgYEAwruIv33GWxGFvQmAUtPaHyhkFOiIpTrmZGeaJe7uLvAF86wK9v35
-wN2fH69JczO1iEWuqDLZ4nEorx6UvjSFBGYTXT1dAnULJ1KHvkk6JjbXq1srTY42
-U2h33XfJiNgrXlj6v7tMhKD/nBsfyw8v4aHxxUkJl2HomPSv7C7EvhECgYEAwTFp
-sUwDXVeputWwBvDUXHgGaVks28QHXvYH7Q0WsbFjb6lZVH/FxLXvGs7aaZk8WuHQ
-JJcXmEkTs1QDMMWoOlZw9WJv5Zlopq31oYHp8dt2EuO/PQcYmXYEG7JIHJhx8mfL
-f3Y4ix/hnvnITTg7bRNpcxwGEqyg16kalP8PXnkCgYEAgml5cVTYLFEV0b21NMMw
-RsGUFPSN3qoNdZx0fYb/+GtCcSf8x+DbDDDfyiZn+EDfB/4ys+4qQR4rcuv2DVO6
-6XE68qyPx39/EryQr/z2dnUwBlAuNehRtZY3ABii3YR3tt28P/89hW0VAgSgTCtF
-k8QS2F7Lj5hAX38u+etwUyECgYBWjf7eckHnpgjjLi3JTki2jQfCVzOj2nW689ul
-NwH95o24T1U4aG6ArUpM5nQwb3j89sK8Qf1OOx9abr9nMIcoa+X76nhbk5mxY6rz
-CzN3Km4CFItvmihJSPiaOAva0+npQtuHZb37hvMcuKgnAJSPT+0kp1+JKlJ9jMPe
-EVAfcQKBgQCDxRNXxn4ILyH3kx8lrch7kD5fp/7KifDjAFFJ0DK2e2xsxLUToh2I
-PdMuzCUv4LL8kRsQ/+mUJY4YlOV9OKVAZbI/gPw9NnzBUBugz+wy0OG/nyS5k7G5
-MIzZm8yx3RieTNhwmw25NDLyGApHGQYsQ7DM/daA/wwdjFsyncxHSg==
+MIIEpAIBAAKCAQEA2e36r+Iguu9/DrzgvzjRZoTlZjDXtkqxrZPuyiL/zHezCSSq
+SMoMHPfRHy95ulhY70kPAgATvRpyyHqAekc1+V/dOHT0MyHpDJBNuyeA+cM2FgJm
+LLvZnf//a42nWgNNcx78baXN9ALE6Dq0Wtkb+y8KLb3KKdmwQl12THYLwwaQrgjp
+d3EN+Oi2QZhA+Nau+z+0oJyPfIAj7ssjFQBU2RODZxVJaFREU3DDKOVcL7VxU+NQ
+ehTqrcoWRWPPVtUnjSpZvp/M7vv2lBeXPbrMcL0M4+/acwRWEvcTGc28ZQZlPdRm
+yxTVN9jvsKdI1+nNTH5bhr0Y3B0KSvoB0USXWQIDAQABAoIBAQCWWYbgHSPzlBOW
+eVyc0Hg3QGx7aisIStP2Kt9NeYP87oAISNFqUmq0+Yu+9iQHGbiRrVe7S45SopKa
+GVnWApcMKsUWlCl9tWFxF4VpH0HuDm2cFZ+kMR1b0ifHbf0NLsYaLEB+7Sr/s4Fh
+rk6LdsnFK5jcIdn9sX/W6WAaND69Ft45sMXuAKPeqwySm7t77nlIEp4lJy/F/rvS
+UUV77UVL/5GqC1HOP1kXXEJDxHtNrDPh0fF49Qo/6JPRmLIOia0yKUS5+8lKHh6V
+J7nWTucyvviKoYvdJHpi9w2+DTI7mT33Go1Ss4QgHihzcURTtfjsn7yOelS6z8XM
+YOxURwoZAoGBAPZpod0Zpjm9trf547reFPLfAJUiKDj1wYXnlpSFAzOf7pSXDrfW
+L5dc3c5x2DiBXUlBu03ZWo7wVOKv7d9ByTrvXEqT1/KTqmHoI6Mzy0rn9wU0FmL4
+ASPcpPMnNStOSmRfe9HITDVvgLYo2wQEu1MXf5rGs5HjTRpEGVdWRkRPAoGBAOJo
+pQ9uRtaLlCF3KXoRr8HCBKI64IWDki7pbbLTglOBbmptQLfE2GPc/FPBsQwR3KBX
+m18A2pc7CRUKzsDavb7SyXTqtP3NF/AguTjGXIv/45QD6A96SStaYR0ZXSA3/uhZ
+rAaSSXxaI3mDTc32pXoXJDp7K+CPc5w00I8u1/fXAoGAe1UPoPyPiGL+K0M1yngR
+gCZBwmMgQrIutHjfk2Kn4ZTw8wpQYY8grt/aXNP6Zv3I1TvDJgneG6EKu5NWueHR
+eGAJj4JEGbPzGaH5BFyOKeXEa6RQeCStXWe4X8OGBzDeZzKrZKqeCjjO8V2tkWtU
+3xfp1GwTwLdGBhmDnYUfEl0CgYEA1vzVF6T4gQtDGtADQ5V91je8nKvZvQ4lhoRD
+lVZAX7j8tvSNSrMRYypZM9Mtoi9n1524vGqcJpR5WFDN6NUM7iFMCMhCGupgO7Vn
+DCFXidzvJgLbna7Zwd/tbWtDQa/KTqmvrwHD49/X5a+n9tapZRiKXznMfUzaU87W
+589sZjsCgYBQvtZpNQRqTDNOSuIJGsloMNp1HG2Keoj/nBQ9idkw29rUqhae7ZAa
+Csk91ix+AMR8nzQSkpHWOk2+RjdVAQ7m87bnQM6mmAJTcipikDfFxNqOWt7JkM2h
+Ydk36LHBIBVLOeqMXrhCHvai3h6efUz7wjGhNxRJ2OGrzWFfLgVsqA==
-----END RSA PRIVATE KEY-----
diff --git a/test/mitmproxy/net/data/verificationcerts/9da13359.0 b/test/mitmproxy/net/data/verificationcerts/9da13359.0
index 5868a304..88ed4145 100644
--- a/test/mitmproxy/net/data/verificationcerts/9da13359.0
+++ b/test/mitmproxy/net/data/verificationcerts/9da13359.0
@@ -1,21 +1,21 @@
------BEGIN CERTIFICATE-----
-MIIDXTCCAkWgAwIBAgIJALzkvKyFAwWYMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
-BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
-aWRnaXRzIFB0eSBMdGQwHhcNMTgwOTA3MDgyMjUxWhcNMzgwOTAyMDgyMjUxWjBF
-MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
-ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEAkvT4Y1ML8Gg4x5aFVygIW022tJsEyfuW4HsEvIarAGpFtUNkw0dOoAxv
-Gv71I0KAWOXxtc9DUjtq7ZXJcG+dBiheTYJ40lrYhHvUt7J37nrUF5v5trQzoE9I
-6WGtzTV8C8RI6F+M6GtwxgwRBgqkuXiK5ExPXAjGMMJZWtiEXHxGTNB6F4zy884m
-VjVtQi8jy+c5g21Awp3z5HrQLM210zwvgi7Rygk6/UM0AnmST4o32SqFSd+0MFUJ
-f3pH3xczfKmhU/TBoVEWRB1YzwixsJrzDOB8wOGnNKCsl45hYUJZZ8epVkyrvFZQ
-iMkwIqqBJbkU6H7fZBl68TJ8ascUCQIDAQABo1AwTjAdBgNVHQ4EFgQUkurgHlw1
-xMP2wrsrGPTk0ofxCyowHwYDVR0jBBgwFoAUkurgHlw1xMP2wrsrGPTk0ofxCyow
-DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcdExVlSvH6aVExNiQO3k
-cMamj+78woDn9x563vwzaGP24KvOXk1B/IJp5kqu3ZsXS0I0Mz6xwXHAXeuxaj06
-cKgEpHKKgClLblXo2zWqo/3V1UFFpOVP/NhI3r21b+fPrS46rP0mw75haQCph8/8
-buQr0OeAYbElliY/ji+cJiCJB8A/D13fUMV/NUUfPW/UE6497jOmz+6PtZNAoOFx
-evrmDcbCzbJxacyLJX04rsrt6DO09jb/+5lFm5Aqr6ySKasrmheIGEisl4o9Zbuy
-5PvYgbOEmFgPATIiWGpBO/rqwDdsmgyYFl+YfFoW0akXUVhDb2e5iRDx6Rs0fmN/
-NA==
------END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDazCCAlOgAwIBAgIUTE2oFYATbkkytGOt0+CIAOPpaeEwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTExMjMwMDA1MDlaFw0zOTEx
+MTgwMDA1MDlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDZ7fqv4iC6738OvOC/ONFmhOVmMNe2SrGtk+7KIv/M
+d7MJJKpIygwc99EfL3m6WFjvSQ8CABO9GnLIeoB6RzX5X904dPQzIekMkE27J4D5
+wzYWAmYsu9md//9rjadaA01zHvxtpc30AsToOrRa2Rv7Lwotvcop2bBCXXZMdgvD
+BpCuCOl3cQ346LZBmED41q77P7SgnI98gCPuyyMVAFTZE4NnFUloVERTcMMo5Vwv
+tXFT41B6FOqtyhZFY89W1SeNKlm+n8zu+/aUF5c9usxwvQzj79pzBFYS9xMZzbxl
+BmU91GbLFNU32O+wp0jX6c1MfluGvRjcHQpK+gHRRJdZAgMBAAGjUzBRMB0GA1Ud
+DgQWBBSTmtfFP/zQDiCbte3AG3vchjSekjAfBgNVHSMEGDAWgBSTmtfFP/zQDiCb
+te3AG3vchjSekjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB6
+CnGmyJKVdD41Qs8QbyZ5VvrAkSq7DkL4CAIDy0pClOYjx51R16g4/dXPd8nuDjVB
+GbRuO/ZHlKeO+RAVbV4s74KeBmQ4uyuZMvuiJGCDtTgu+fbgp1l41vj21bkDnz/X
+0aVrwC4clh6O1/AFCY7cYygp22QJ0zGpIb9d0ly6QDMEocHJKasuQ8/0GPvv6wkY
+hwNub2ScsVFHPrFzzufZblBk3Ks0YZw+IsCMTY7QtGb1TZhGeX9xAydqNMnmIxNj
+XOE2FKx4ISQNFwf/U1LEAruO7PjbbsWx5FHr4oYV9TCLoERZFofwcGGM9o/cPc4P
+CH8fGvFYoU30PG+xX0Pc
+-----END CERTIFICATE-----
diff --git a/test/mitmproxy/net/data/verificationcerts/generate.py b/test/mitmproxy/net/data/verificationcerts/generate.py
index 8439c9e6..1e09138d 100644
--- a/test/mitmproxy/net/data/verificationcerts/generate.py
+++ b/test/mitmproxy/net/data/verificationcerts/generate.py
@@ -5,10 +5,10 @@ import subprocess
import shlex
import os
import shutil
-
+import textwrap
ROOT_CA = "trusted-root"
-SUBJECT = "/CN=example.mitmproxy.org/"
+SUBJECT = "example.mitmproxy.org"
def do(args):
@@ -18,29 +18,39 @@ def do(args):
return output
-def genrsa(cert):
- do("openssl genrsa -out {cert}.key 2048".format(cert=cert))
+def genrsa(cert: str):
+ do(f"openssl genrsa -out {cert}.key 2048")
-def sign(cert):
- do("openssl x509 -req -in {cert}.csr "
- "-CA {root_ca}.crt "
- "-CAkey {root_ca}.key "
- "-CAcreateserial "
- "-days 7300 "
- "-out {cert}.crt".format(root_ca=ROOT_CA, cert=cert)
+def sign(cert: str, subject: str):
+ with open(f"openssl-{cert}.conf", "w") as f:
+ f.write(textwrap.dedent(f"""
+ authorityKeyIdentifier=keyid,issuer
+ basicConstraints=CA:FALSE
+ keyUsage = digitalSignature, keyEncipherment
+ subjectAltName = {subject}
+ """))
+ do(f"openssl x509 -req -in {cert}.csr "
+ f"-CA {ROOT_CA}.crt "
+ f"-CAkey {ROOT_CA}.key "
+ f"-CAcreateserial "
+ f"-days 7300 "
+ f"-sha256 "
+ f"-extfile \"openssl-{cert}.conf\" "
+ f"-out {cert}.crt"
)
+ os.remove(f"openssl-{cert}.conf")
-def mkcert(cert, args):
+def mkcert(cert, subject):
genrsa(cert)
- do("openssl req -new -nodes -batch "
- "-key {cert}.key "
- "{args} "
- "-out {cert}.csr".format(cert=cert, args=args)
+ do(f"openssl req -new -nodes -batch "
+ f"-key {cert}.key "
+ f"-addext \"subjectAltName = {subject}\" "
+ f"-out {cert}.csr"
)
- sign(cert)
- os.remove("{cert}.csr".format(cert=cert))
+ sign(cert, subject)
+ os.remove(f"{cert}.csr")
# create trusted root CA
@@ -54,13 +64,13 @@ h = do("openssl x509 -hash -noout -in trusted-root.crt").decode("ascii").strip()
shutil.copyfile("trusted-root.crt", "{}.0".format(h))
# create trusted leaf cert.
-mkcert("trusted-leaf", "-subj {}".format(SUBJECT))
+mkcert("trusted-leaf", f'DNS:{SUBJECT}')
# create self-signed cert
genrsa("self-signed")
do("openssl req -x509 -new -nodes -batch "
"-key self-signed.key "
- "-subj {} "
+ f'-addext "subjectAltName = DNS:{SUBJECT}" '
"-days 7300 "
- "-out self-signed.crt".format(SUBJECT)
- )
+ "-out self-signed.crt"
+ ) \ No newline at end of file
diff --git a/test/mitmproxy/net/data/verificationcerts/self-signed.crt b/test/mitmproxy/net/data/verificationcerts/self-signed.crt
index 6e234b8b..a02d9f32 100644
--- a/test/mitmproxy/net/data/verificationcerts/self-signed.crt
+++ b/test/mitmproxy/net/data/verificationcerts/self-signed.crt
@@ -1,19 +1,22 @@
------BEGIN CERTIFICATE-----
-MIIDEzCCAfugAwIBAgIJAKzH8k6aKTP6MA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNV
-BAMMFWV4YW1wbGUubWl0bXByb3h5Lm9yZzAeFw0xODA5MDcwODIyNTNaFw0zODA5
-MDIwODIyNTNaMCAxHjAcBgNVBAMMFWV4YW1wbGUubWl0bXByb3h5Lm9yZzCCASIw
-DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOjJMp2o5eLQEmYJqMZzLBi61h9
-fsCVMvS8hgrH1Cg5q/RaLBLrZ8nILKmFZBapMUEFkUwQLB864tdTMaX7p+jNv3sM
-5LWEIYkTIbu6qV7QerKdubS1hpdFtQGRM1Q+C7H86FzF02DSKzNSmQc4fNed/lQM
-qo/jOm1xx4TZFR4j58BrmmoOfNP44IyrwXsPyXbMsukKixVEB3vQ2oyGDAyG6dYi
-VvM8PVL5yhX3BJ0D1Ky6hgGHJeirm0Cd8qqdSC/SWNdu1bGzg/xyUX5XFaHlIi7Y
-5YhD7ZDLvC76MeCWkfo4DaSB0CWmtG4l1TtHM2JqP8qf2l2LsABKs0q/a+UCAwEA
-AaNQME4wHQYDVR0OBBYEFIc9YAXgnGRhPTEcN/j+k/dxMdKqMB8GA1UdIwQYMBaA
-FIc9YAXgnGRhPTEcN/j+k/dxMdKqMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
-BQADggEBAD9qKci3Pr4/2WGx+sv8gOpKchC9eF2dXc5hA3xbDw7T6oRLUBAY8Pty
-JF7DHMovT+w7FPRYT8rSc190fbSwVRHAnEaqAzaxteImCp/qYgdBHOz39eG4c93W
-YrYvA1VdUDPcUnisEVWguDsKJGFg+G6pw+8Wkf/hCrJJkriTFogGvzg6ptdQatvE
-dpSkionfbuZKz+7lny6sCBGoMRIFBd22MHJsSQOyTb06Lwc5dpdF9c5vysPRzShJ
-5kkgIjGpTmWp+Ud8BAMQH8EDhJMkJ7iw1+07UQ9MUmXCp9Xgim6x1ri2/yoz9HeO
-83VCkD9YWufrzOrsXpo04rMYtoKo+lw=
------END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDjTCCAnWgAwIBAgIUb0mIVHKB+bu2PGObggokU3Re7xMwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTExMjMwMDA1MDlaFw0zOTEx
+MTgwMDA1MDlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDF6eqyZD2z83buUF4T+6AY0Zoe925a2AHOhGtHJMLo
+9AD7FF1Xi1iksEvxbOI6mreHtvYKzUpfNsA3DkFdSO91HMSkdvWcDcExpW62sNK9
+gQQrpcCx7DogOrFiGSIHI1LIy1y6YEJma3G71SgGbw7g1QF64dTX9+BzVQJsloT2
+H3ZxTi8Fb6APJq6d/Tp67GTM8U82vM+FjLKzfH7RMSEPvSyvWSibw3AZP6owFaxz
+DJtXpR7evOZbiZxqXmGOBl5OQPu9GdDA3Fyi9Drp7xa234loqd1a8PYyL8qWV/2G
+HM3IJOzG8Y5PUJhL8CkDbx4LJ9LfzeSuBnQPUf2ZNalZAgMBAAGjdTBzMB0GA1Ud
+DgQWBBQbkS0mAGPD4XjOv9GKzGMyR9ix3TAfBgNVHSMEGDAWgBQbkS0mAGPD4XjO
+v9GKzGMyR9ix3TAPBgNVHRMBAf8EBTADAQH/MCAGA1UdEQQZMBeCFWV4YW1wbGUu
+bWl0bXByb3h5Lm9yZzANBgkqhkiG9w0BAQsFAAOCAQEAqZ64xpu3qrTlCc555OoP
+GgobUFP3qv07d0/r48cOyYdAdlEHhvmDPlqWTB9e4ZYtWZMlocY9DpCywzKTa7F7
+Ad8BwS0a/No3wVdl1UEkIGYxuD//jbd77Mrpf5URvQco85o/bicn+H0GAOchYt1P
+jP1VShqsRv6WiTs5kn1/JwVoafddl1jBlMDmCqDv4loAZJYHzie0CqdjjSeorFfE
+8FG8OLwmEnmIW6VnanRH8coH9MBbZ+dRtCavS+Q8s0R77dJM1sCp5/4yKcr5D/PD
++dQN9f+iugLxDdBQjiRyadWX9/l4n/h8ezabZ4cNsiWbRXrp5VS0nGNmAK3XvPAu
+ow==
+-----END CERTIFICATE-----
diff --git a/test/mitmproxy/net/data/verificationcerts/self-signed.key b/test/mitmproxy/net/data/verificationcerts/self-signed.key
index 3546c3fb..b6da998f 100644
--- a/test/mitmproxy/net/data/verificationcerts/self-signed.key
+++ b/test/mitmproxy/net/data/verificationcerts/self-signed.key
@@ -1,27 +1,27 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpgIBAAKCAQEAw6Mkynajl4tASZgmoxnMsGLrWH1+wJUy9LyGCsfUKDmr9Fos
-EutnycgsqYVkFqkxQQWRTBAsHzri11Mxpfun6M2/ewzktYQhiRMhu7qpXtB6sp25
-tLWGl0W1AZEzVD4LsfzoXMXTYNIrM1KZBzh8153+VAyqj+M6bXHHhNkVHiPnwGua
-ag580/jgjKvBew/Jdsyy6QqLFUQHe9DajIYMDIbp1iJW8zw9UvnKFfcEnQPUrLqG
-AYcl6KubQJ3yqp1IL9JY127VsbOD/HJRflcVoeUiLtjliEPtkMu8Lvox4JaR+jgN
-pIHQJaa0biXVO0czYmo/yp/aXYuwAEqzSr9r5QIDAQABAoIBAQC1RpgymlffdgJt
-rvQuMRu/XQlhh3dJj3YV3BIAL0VguH+i/WLVbRdQm5D2y0kAzml7LGODrYCUt4W1
-q7rXaCYfy3Xf2QSbRQGl9/pL7xw9ZMQseYW38nPx+39LInYDWzKPDB9qx0uj7Vpm
-ReTSEf9r81PUIaBxj0V2X/VWHag5scBjXoflQLxyV6i1UvTuWyhYvX1Bbaj02MqV
-tGNMrjbj25Wx2za53VDonzNA6RMZzkWGMfzmkkGM6kjGzLEsveys+bYCt7Fs7slR
-4oby0bIUmN7iqLhqlEhS4weWW4iHlq17X7CZeQAE1XeVZBz1N4G8FLjND2eyqb2N
-RAcQqp3BAoGBAOiU3WTu/kSccteDRxR1gVRPqEgfoLDwyAb7ORVUWX4Ii/z/soMw
-xZ2MlYPLnp3Fyu/hKhJPC1LzkD4CGHCTJJ1NnUudtDxl2Zh1FZYGmv1hi1TID/cm
-G0+3XhlJgztS41+AzxTNMulV1yieT2HIRIoRpdSx1UIA72l42YqjrwUNAoGBANdV
-/Ib+3hAfFtSMMI1qZQXvlKEoDRbUOCYuBVkTK8oQJQH6MLDokHZ8sXBAqi9383b1
-XmhQBJZ//yMy0AqFa2QBlkK0Gizzhh7BLSjIT2LREf66B2cWzhgdhbSp6Nuk+3DK
-NfibxsFAPpW05HqtfxhbjrLfoE8VvTuMGQ8AaXw5AoGBAISo7IL2wrdV2TdN3Mwx
-ndv+N4kz6Q8jt6QrxUqCOy1lKJvdKPAlcIJFvr5W9RkeyXr7nmilB1uAK4UC4vfL
-JfZHX/HSeQx+N5f7KJ3TFLJz4eow1tJsvOVCPP0FbkH3LFO7/+HojSKEYN39NmAa
-v+VU3Zas/GvSZrxtPwASDvE9AoGBAJOBbluW6MzITx5H7dZhRFR9miWOxvCVbOUS
-b01mKX/f8UnadVIp7RONNQr88NdVZqxdRk9USOBDS6Vz4DjkzfySbbjBoJCcPIqC
-r4mZNXAuYRJJolqGr6SrTHTGUyFqcWcAzVnAc7TbakOoxz4V7NLlnOmA8FJcROUu
-gdfZ42hZAoGBANyL2IQ+L92iYVvqsz1zPBlvemevx8zP6GmlzvTcVexyDTIiLg6W
-BVil5zRDPJdDiFfBK18Qg1mJoE4SjLTg+yGww9ef37Zb9kZypy6pM6AbRWILZ1Gv
-7UsWUzk6rgcQpDdpJCUEt+AD3LQJTxxuoIhZePvC2GLkzsjZA7ZyB5+S
------END RSA PRIVATE KEY-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAxenqsmQ9s/N27lBeE/ugGNGaHvduWtgBzoRrRyTC6PQA+xRd
+V4tYpLBL8WziOpq3h7b2Cs1KXzbANw5BXUjvdRzEpHb1nA3BMaVutrDSvYEEK6XA
+sew6IDqxYhkiByNSyMtcumBCZmtxu9UoBm8O4NUBeuHU1/fgc1UCbJaE9h92cU4v
+BW+gDyaunf06euxkzPFPNrzPhYyys3x+0TEhD70sr1kom8NwGT+qMBWscwybV6Ue
+3rzmW4mcal5hjgZeTkD7vRnQwNxcovQ66e8Wtt+JaKndWvD2Mi/Kllf9hhzNyCTs
+xvGOT1CYS/ApA28eCyfS383krgZ0D1H9mTWpWQIDAQABAoIBAGl4H8+TZeJ5E18q
+ywfhJ08ym+x2tYOJ62SP4s+WApy8M62aC6g0pTeWj9IH0YOjobycPwBAqKqW9dYh
+Lao1zQ5fF1gB4R+ZoOQBIkAPeS7uCzfrbAYlOlCklpUNibm+FEbXQQI9fAUyqviL
+Pno3QvmD6fb/VDsHaMBthA40JIU4C3yUUcSooKKUC3XKmbRDg5/stKbVdi6co5AQ
+OMNFj4smts3rXtk5tKMBep4AgKKdClf4IuhpWEqZDxN6e+hZA7g1VOmOIE+hKEEO
+ODBkjHNgHIFbsr8GTlZ+GSkHBxMNVdHxntJTU5a2jPiTNTTnhqu2hcVdkyDwLb9x
+TGUCdyUCgYEA/ZPwI2KaT4Lj8b+6DCV4v3nZF8nLCtt1LiXxJWexdnr+3q/ZfNjN
+5sKVl9rIynz2zoJDVr1MI8yjO6OmV7GBW2wgJsSFwg0vnV/bs75KqPZWZXqy/mrp
+HnXd82XUweyyOpmBg4cCjmcnMTrdvCHvfgx7JafnUDqeXNwriWeC5Y8CgYEAx83d
+qHgAT2rlnCQYRmCOGb5fuyQ+pSM4p8h1AjueLrTNiWcqX57ct9eVWjymD+AgevFZ
+hZH9m6TI4nCb/5ukcvjQb4g1kyY3l7rZiiJ3kf/c22kVsVUagJGeiW6Ypnox4r1o
+oFXEwAw1qSGIqOIjYVB2DqvjUl3+UjWCl9SOnpcCgYEA0TSdWUQ/VTwCvW9VmjHM
+FgT8I5Ebj+CRI7qv4hFTqxE8dxKTl1nzPd/ptTgOkmhY4vU7gzN3vs1VGp4gXZcX
+xwpE2Fcol3lzgB4Wz4s+Y3mgu+ZoCFjB7ZyGugmYZ0nVnV0KKi5X4I6gGhCb4VwK
+D29SpjWJNHq4LpqC3MDmkGcCgYAxnKOKXmmtTpy+3ZONfhIqwEOjA0fu10UNHFA5
+grYvYMOcd5pk7dxeZdB2/JI7ZOqLvHv/F5YCXLNozo9ds7bsuW2AFDFBXX72VPYJ
+P6+y9/ZOINS7GKeg/wd/lo+e3r6eT2u4TDOzgBSe722wiZ5BXqpB0Fp8rEwm+5R2
+wNe89wKBgQC59SOa4EW3gfjLRaH6hEPcMEzjr0Dt5ilBgntYbI+DsCePwEX28hAK
+fd7A6XoCqjnwLpAmh0cmNRsYjtPCzg6TXUdrhfnR/3r7DHzvsxaC06BfkPZJ0+Fu
+rP3el/oKSPaDsZKjW6RA5oqlIF82o45MNl2oCG82LgVtu25e3HbJSA==
+-----END RSA PRIVATE KEY-----
diff --git a/test/mitmproxy/net/data/verificationcerts/trusted-leaf.crt b/test/mitmproxy/net/data/verificationcerts/trusted-leaf.crt
index 3ccae708..d71cbd07 100644
--- a/test/mitmproxy/net/data/verificationcerts/trusted-leaf.crt
+++ b/test/mitmproxy/net/data/verificationcerts/trusted-leaf.crt
@@ -1,18 +1,21 @@
------BEGIN CERTIFICATE-----
-MIIC4TCCAckCCQCj6D9oVylb9zANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
-VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
-cyBQdHkgTHRkMB4XDTE4MDkwNzA4MjI1MloXDTM4MDkwMjA4MjI1MlowIDEeMBwG
-A1UEAwwVZXhhbXBsZS5taXRtcHJveHkub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOC
-AQ8AMIIBCgKCAQEAqKzVdsRKgthv6V/dk3Tncy4ymbACs383nGutjulExvroNOCw
-b0y0e7unNGbtXxFQqSvA7eGaT1yRNfoMbXGSS+sn8A3gB6/s2A0Sw7KeSDdoaqEq
-F/LzRBed1YkxSyy0GXuTd7HXNIoFn/eF1tqxgViWdfyFD85qY4yJ+luofdm7IcPM
-ENPzV4nKzDh2PdJpQrEokWz2jM0zefC3IYnFHXY5bA3MnhE03/P0VxeEYkBdmEAt
-O1U2Bkw9SKCLy9zF13ks6/dDZ9LjMtRKI83gQS5z3S3bA45YxFuyeLWgVsJ2NYTa
-j9/8c4xwOjg9TpkCvcmZiPUYGddPHWoKqAAhBwIDAQABMA0GCSqGSIb3DQEBCwUA
-A4IBAQAf8cjxunN4Y7NUD2Z/SNOJ/s0uWJtTPV6m4FxSwwD0wfbsyirPchmattLc
-BabrQkeMMm8gMOrORfanXQwvLZvX0aDf96EgLSfHv8Iqeol5Byrgkn7UORXl20Jt
-8UNRURUZYtWxn08P8dlhxQUncPF/UxCesC8x0cihqv+YTB3TX1sni9mOqPCYY8yH
-E8kCW4zTJ0J9OQUHq9qdYQM/PGVm99+DWBItUeZAva8Rqj1FN3f9j1eWB+EjfYu7
-ztsTInpNWP4tIh6vIFtuaGr077cJawTe6YVyNxVqquI9+2fpSPkt7tCTIhbQ4AmM
-DeHzn+KjfKN8ooWqmcfmUZWaADe0
------END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDajCCAlKgAwIBAgIJAKPoP2hXKVv4MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTkxMTIzMDAwNTA5WhcNMzkxMTE4MDAwNTA5WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA4BZUGKkf52583Kps2SinCzRtGFaGIqtP1YfjaN1H21c08hEERRWf0GqW
+PIbZz0NRyqOVM0O+4A9cxKrdqDpALBNXdgB62Ven1NNE31ZjsbFgwbORPTiOQo+6
+Xsu+2KHQdqAGOXSqOdNSoZV/HqAKa41iweg6lUf3yrO/hpfnlXTFbuM/oYFARrr9
+YWY7qH5BEvcZBf4Sg3cWGjdbzYg8S6hnFBBkHPDMbS3xra7H8uru8aG9L70aiq0C
+3WsGf1/Qctz3cccB6BdnbiXji0QlP0miT+b52hEzDvNjdXvoObgbOZsUUcLrbz8q
+LqaQPj6TA2PYpKcxEdJLDcbN++tglQIDAQABo10wWzAfBgNVHSMEGDAWgBSTmtfF
+P/zQDiCbte3AG3vchjSekjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDAgBgNVHREE
+GTAXghVleGFtcGxlLm1pdG1wcm94eS5vcmcwDQYJKoZIhvcNAQELBQADggEBAMoK
+8+KTYF10dbQ8Hl3Fq4Iux7eutAPuKUXUz9dnCfDRhZukdl+gahRoNvkyMBdXOvvx
+R9Z2+Guo8NOYKgT1mJtS/c8IxTkjZj9doujJOVD2wTowSfoaT9z+/EJa+6Fp9+xd
+YVsO3E/2Vxai8PCNx8JTXr2axcnBDvpHPRXF21hOI8N94SPAcmLTZsdsTELjrGGa
+/BA0y+pCEwW6cY9mMHVAAvRoMoqfocBVI7nrYBaQfFoKuwxscxO679eEv+lbSjul
+z/VNdWfqrDhFFSzwRSVchapQ9q1EeTzv++wZRwI5bT0Ib6DFTJB3J5+9RihlFYfU
+GI17CI0D//DsFic7QJ0=
+-----END CERTIFICATE-----
diff --git a/test/mitmproxy/net/data/verificationcerts/trusted-leaf.key b/test/mitmproxy/net/data/verificationcerts/trusted-leaf.key
index 7db3186e..e62ee61f 100644
--- a/test/mitmproxy/net/data/verificationcerts/trusted-leaf.key
+++ b/test/mitmproxy/net/data/verificationcerts/trusted-leaf.key
@@ -1,27 +1,27 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEAqKzVdsRKgthv6V/dk3Tncy4ymbACs383nGutjulExvroNOCw
-b0y0e7unNGbtXxFQqSvA7eGaT1yRNfoMbXGSS+sn8A3gB6/s2A0Sw7KeSDdoaqEq
-F/LzRBed1YkxSyy0GXuTd7HXNIoFn/eF1tqxgViWdfyFD85qY4yJ+luofdm7IcPM
-ENPzV4nKzDh2PdJpQrEokWz2jM0zefC3IYnFHXY5bA3MnhE03/P0VxeEYkBdmEAt
-O1U2Bkw9SKCLy9zF13ks6/dDZ9LjMtRKI83gQS5z3S3bA45YxFuyeLWgVsJ2NYTa
-j9/8c4xwOjg9TpkCvcmZiPUYGddPHWoKqAAhBwIDAQABAoIBABsUC/zSDEgvKOAl
-RLP8a3+hJfxoNjbMsIfK/YTYy/LJqud6PrjPbpYCjRgrgeXmKLXP0VwfAJ/G84Tf
-zIjxV5Qaf0HZaGKzimkwyBdkoGZlhry/fLt1hDolNHBoYuJ3nb4NiaIIiczkb3y7
-xt+0IhTqvNTaIh5ke83ZbPklJ8p0HAzw1q6+iRFZiZKH1iVRwJyIyK654wpNQ93W
-SqUKa3uAbqw+Bx9fzyEunANFwoBcZka9oSR9bTlhGB8HPHZFVYKgZvE4n9WOclKW
-E75pGG6vFYZkxBdqcjFNlPKKZRisDuey28teiHXThh1MvYxRdaMq4oxOE1J+n17k
-F2gQolECgYEA01j1FlExu45+U36n3tCCyS50dTtf0Qpi71c1s5DZyT+AAB2ZSGXm
-VBtKgVRNg/iWfHn5b/zHF30OtgIzcsrU66cWMwIXPUQigXh8Cteve7VMs03hce1w
-wsFwLoyvdWEam32YAymqRgN3H6JQim82IJJ3YlWrgEytBnvLkADCihkCgYEAzE/g
-8aoxDZJUwbvaZjLwuydmvc+aAwanVgqvtkca4x99oPhNQna6O0jXXAtJXMA3SHp5
-QYMDKh98BqCXyfXd+1Semc9pgAPz7l4j09WG7Zdap3xinTOkUsmTAz/2T967HIsP
-6qP7RUiwjmbUk8ZGcKsNjoxzPA4JURYimKB5qB8CgYBTjlnnJtaYpi8/Z1WK+7iZ
-PSqBpqWtCYQvx7TNdzkDHX3Hjewp+U9kdR2xn9i9kiw8riR1p+Q2XxTP1HLusU4Y
-lIhsRilV6XgS48V2q+sO55CZWvMEjbEE7mEhpjFAINHaI39T0Mcmwvv3n75j3K/z
-lLRqRiB1qtrFM3A5UHOZEQKBgBPQm2xUqTU7v+SaJ3BJ+HbuN1SpUbKBbrE1kB0J
-gF4Oq8x0yGltwloFkn1mytKoAbSRzDjCUAhBzXGHGbGImuLJLiiUqRK1T28Kyka9
-KrzYNP6RXa8JVyKAUjW6elT8sQDvq7eB99icWCM3bd53GFXNAR+WF4b3hYfLscdD
-qQjZAoGAB3ah068Qscb2Ef3+eufa+EvOfMDrNNvlEoZXRlhviTg3NEwjyqbxaCIy
-6Xg+rWvgJm9UE2RBOcCNeghkEabmL1+8DvmDiV1lt9oJtqULBirvalp2H3+9yiTk
-j5dnVcRF6cYvNFmwTr2WZFBGgq96d/Zmbx3o3MIqSBc8I6pLNzo=
------END RSA PRIVATE KEY-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA4BZUGKkf52583Kps2SinCzRtGFaGIqtP1YfjaN1H21c08hEE
+RRWf0GqWPIbZz0NRyqOVM0O+4A9cxKrdqDpALBNXdgB62Ven1NNE31ZjsbFgwbOR
+PTiOQo+6Xsu+2KHQdqAGOXSqOdNSoZV/HqAKa41iweg6lUf3yrO/hpfnlXTFbuM/
+oYFARrr9YWY7qH5BEvcZBf4Sg3cWGjdbzYg8S6hnFBBkHPDMbS3xra7H8uru8aG9
+L70aiq0C3WsGf1/Qctz3cccB6BdnbiXji0QlP0miT+b52hEzDvNjdXvoObgbOZsU
+UcLrbz8qLqaQPj6TA2PYpKcxEdJLDcbN++tglQIDAQABAoIBAG5V8DB4TdI5T9ej
+Ppcqch2NQc5DBCbb7SI5l5qRogj5BoPOJykQ/bC0WqcQyvxHrGU3aIZma/yM8+OO
+Mjfb/q71ExJyKAsOIwAiyn2hXtMmgHq/vNrFFx7lACIe9ihafHd8UbRGom54g+41
+2vKsYJUWd7L8cqQAXJz9JmfSMeAfRBVVLyOpENSVT8gM/LgPokmZbg0wRJTA5oa3
+10f4Ax1HqXO3cQgKKnrvIU72gB7MQ9Y2G+P2eAmiAlzGr41N5sv7uFz0JrFHhqtC
+qc7QrGVVNtxanu0s/LWgfQ+LrKldTysX16j/j6J0tg7Pfuq9cMFPklU98dv2LF5J
+jMB8eQECgYEA84+Yy/IGVjUPVb3HjmCmWUoxt4Aqop/12/4+1cBNS6h58iLJpcWc
+FVh8m3UbuE7shimEg0fZ1rPPkqepKlDw8/sKXDni/khBASOAh2v1RhvOxNGjxDfn
+ZvcK+BG99kU8ZWQHpKmn8OGfYswL2Fvo67L9XFg/wDnk1QpJx7E1GVUCgYEA64gg
+G6qnsah1NKSW64BPVKi/Gby83l4oa1amjHqq/Unq6iuGB4/QhK4BzW0aOGI7RMTE
+SPh5NF5ZhfT3LWD/EnaMm6QxMTSsL9TDIg36WTVZTkbODFOJczg19EdTs1pa6OXW
+0NM6psLu6Nf/QmHJzyYxSZ83uqPOM305xgooKkECgYEAnA0XMySglr9sUd1EbJ7U
+NkVpUU8XAhdHKWrey4lofN83MsLDPCk+dha5z8jat94pgVQ8iPiSRBP1HNu7cVdm
+6ouf+bNFEvMsYxRiF2I+RmsuscA4E1JWOwxxxLtpYM6/gZ7znrbs2VNWEbD2retF
+cy69UltgjUMKsMzktMN/Z/kCgYAbzV285lAVMIVlSWhnNCYpICIur5C7zvGGehv+
+yRwV+fu42JphmiBLCR89WHuX3ECSxYdF9c6Y1+pJXbkvqhtx2nyOgrsry8PngX3n
+Ly82CI4aJ1F7MwEukJwN0b2XljrU8wyAae6qcKgy5AxFkbV4tlFrF1hEt8FHYqjH
+L7u+AQKBgBnN2vjOVagtlZjJadg5ueR7uKnfk7/TGMtZjNf89C1UZH3TqWnnj6p6
+rLLe4tnA3EebFtGeVWlmnw5k166HRBEd7KuebOCq3hkNlf71aHwhDJ0bIdDwo1g+
+FTpG2vqkqpuEb+2FzDjZT5EdPnj8tUGthREDXpXqsdjFb6+2Enjx
+-----END RSA PRIVATE KEY-----
diff --git a/test/mitmproxy/net/data/verificationcerts/trusted-root.crt b/test/mitmproxy/net/data/verificationcerts/trusted-root.crt
index 5868a304..88ed4145 100644
--- a/test/mitmproxy/net/data/verificationcerts/trusted-root.crt
+++ b/test/mitmproxy/net/data/verificationcerts/trusted-root.crt
@@ -1,21 +1,21 @@
------BEGIN CERTIFICATE-----
-MIIDXTCCAkWgAwIBAgIJALzkvKyFAwWYMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
-BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
-aWRnaXRzIFB0eSBMdGQwHhcNMTgwOTA3MDgyMjUxWhcNMzgwOTAyMDgyMjUxWjBF
-MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
-ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEAkvT4Y1ML8Gg4x5aFVygIW022tJsEyfuW4HsEvIarAGpFtUNkw0dOoAxv
-Gv71I0KAWOXxtc9DUjtq7ZXJcG+dBiheTYJ40lrYhHvUt7J37nrUF5v5trQzoE9I
-6WGtzTV8C8RI6F+M6GtwxgwRBgqkuXiK5ExPXAjGMMJZWtiEXHxGTNB6F4zy884m
-VjVtQi8jy+c5g21Awp3z5HrQLM210zwvgi7Rygk6/UM0AnmST4o32SqFSd+0MFUJ
-f3pH3xczfKmhU/TBoVEWRB1YzwixsJrzDOB8wOGnNKCsl45hYUJZZ8epVkyrvFZQ
-iMkwIqqBJbkU6H7fZBl68TJ8ascUCQIDAQABo1AwTjAdBgNVHQ4EFgQUkurgHlw1
-xMP2wrsrGPTk0ofxCyowHwYDVR0jBBgwFoAUkurgHlw1xMP2wrsrGPTk0ofxCyow
-DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcdExVlSvH6aVExNiQO3k
-cMamj+78woDn9x563vwzaGP24KvOXk1B/IJp5kqu3ZsXS0I0Mz6xwXHAXeuxaj06
-cKgEpHKKgClLblXo2zWqo/3V1UFFpOVP/NhI3r21b+fPrS46rP0mw75haQCph8/8
-buQr0OeAYbElliY/ji+cJiCJB8A/D13fUMV/NUUfPW/UE6497jOmz+6PtZNAoOFx
-evrmDcbCzbJxacyLJX04rsrt6DO09jb/+5lFm5Aqr6ySKasrmheIGEisl4o9Zbuy
-5PvYgbOEmFgPATIiWGpBO/rqwDdsmgyYFl+YfFoW0akXUVhDb2e5iRDx6Rs0fmN/
-NA==
------END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDazCCAlOgAwIBAgIUTE2oFYATbkkytGOt0+CIAOPpaeEwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTExMjMwMDA1MDlaFw0zOTEx
+MTgwMDA1MDlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDZ7fqv4iC6738OvOC/ONFmhOVmMNe2SrGtk+7KIv/M
+d7MJJKpIygwc99EfL3m6WFjvSQ8CABO9GnLIeoB6RzX5X904dPQzIekMkE27J4D5
+wzYWAmYsu9md//9rjadaA01zHvxtpc30AsToOrRa2Rv7Lwotvcop2bBCXXZMdgvD
+BpCuCOl3cQ346LZBmED41q77P7SgnI98gCPuyyMVAFTZE4NnFUloVERTcMMo5Vwv
+tXFT41B6FOqtyhZFY89W1SeNKlm+n8zu+/aUF5c9usxwvQzj79pzBFYS9xMZzbxl
+BmU91GbLFNU32O+wp0jX6c1MfluGvRjcHQpK+gHRRJdZAgMBAAGjUzBRMB0GA1Ud
+DgQWBBSTmtfFP/zQDiCbte3AG3vchjSekjAfBgNVHSMEGDAWgBSTmtfFP/zQDiCb
+te3AG3vchjSekjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB6
+CnGmyJKVdD41Qs8QbyZ5VvrAkSq7DkL4CAIDy0pClOYjx51R16g4/dXPd8nuDjVB
+GbRuO/ZHlKeO+RAVbV4s74KeBmQ4uyuZMvuiJGCDtTgu+fbgp1l41vj21bkDnz/X
+0aVrwC4clh6O1/AFCY7cYygp22QJ0zGpIb9d0ly6QDMEocHJKasuQ8/0GPvv6wkY
+hwNub2ScsVFHPrFzzufZblBk3Ks0YZw+IsCMTY7QtGb1TZhGeX9xAydqNMnmIxNj
+XOE2FKx4ISQNFwf/U1LEAruO7PjbbsWx5FHr4oYV9TCLoERZFofwcGGM9o/cPc4P
+CH8fGvFYoU30PG+xX0Pc
+-----END CERTIFICATE-----
diff --git a/test/mitmproxy/net/data/verificationcerts/trusted-root.key b/test/mitmproxy/net/data/verificationcerts/trusted-root.key
index c690751f..11a4d5a6 100644
--- a/test/mitmproxy/net/data/verificationcerts/trusted-root.key
+++ b/test/mitmproxy/net/data/verificationcerts/trusted-root.key
@@ -1,27 +1,27 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEAkvT4Y1ML8Gg4x5aFVygIW022tJsEyfuW4HsEvIarAGpFtUNk
-w0dOoAxvGv71I0KAWOXxtc9DUjtq7ZXJcG+dBiheTYJ40lrYhHvUt7J37nrUF5v5
-trQzoE9I6WGtzTV8C8RI6F+M6GtwxgwRBgqkuXiK5ExPXAjGMMJZWtiEXHxGTNB6
-F4zy884mVjVtQi8jy+c5g21Awp3z5HrQLM210zwvgi7Rygk6/UM0AnmST4o32SqF
-Sd+0MFUJf3pH3xczfKmhU/TBoVEWRB1YzwixsJrzDOB8wOGnNKCsl45hYUJZZ8ep
-VkyrvFZQiMkwIqqBJbkU6H7fZBl68TJ8ascUCQIDAQABAoIBAC/1mopvs9nFaaJZ
-UTLccb26YwIWBT4VyWuBOk58dJoyFIXPdLb2MoaxCCF7S20yasiYYoW/Gm1fzsmy
-tIbpJgm4au5Iwj2EQF0cPJOmvtUpaMY7tQcXUDHlLhpcMmhiKBV+/Xw4krfXOHqp
-vXSHTLLq0Akpjkyu4F9RTfAD8U5tEbpPsCGcsSJEHxPgqDexITDwB/yuhvrKKUwY
-t8WQBWO5M8D6Z1HGTFovIa86eX4hUKKbNB8sE7yi1wGxbOloIOQESOcqiisP5GGN
-d6r5k9jBwZXlyh7GR+GNILF+n6ctdOFr6MQQEKvDzjh/IVYADen19909Ed7Wn0gR
-C0Ec2gECgYEAwruIv33GWxGFvQmAUtPaHyhkFOiIpTrmZGeaJe7uLvAF86wK9v35
-wN2fH69JczO1iEWuqDLZ4nEorx6UvjSFBGYTXT1dAnULJ1KHvkk6JjbXq1srTY42
-U2h33XfJiNgrXlj6v7tMhKD/nBsfyw8v4aHxxUkJl2HomPSv7C7EvhECgYEAwTFp
-sUwDXVeputWwBvDUXHgGaVks28QHXvYH7Q0WsbFjb6lZVH/FxLXvGs7aaZk8WuHQ
-JJcXmEkTs1QDMMWoOlZw9WJv5Zlopq31oYHp8dt2EuO/PQcYmXYEG7JIHJhx8mfL
-f3Y4ix/hnvnITTg7bRNpcxwGEqyg16kalP8PXnkCgYEAgml5cVTYLFEV0b21NMMw
-RsGUFPSN3qoNdZx0fYb/+GtCcSf8x+DbDDDfyiZn+EDfB/4ys+4qQR4rcuv2DVO6
-6XE68qyPx39/EryQr/z2dnUwBlAuNehRtZY3ABii3YR3tt28P/89hW0VAgSgTCtF
-k8QS2F7Lj5hAX38u+etwUyECgYBWjf7eckHnpgjjLi3JTki2jQfCVzOj2nW689ul
-NwH95o24T1U4aG6ArUpM5nQwb3j89sK8Qf1OOx9abr9nMIcoa+X76nhbk5mxY6rz
-CzN3Km4CFItvmihJSPiaOAva0+npQtuHZb37hvMcuKgnAJSPT+0kp1+JKlJ9jMPe
-EVAfcQKBgQCDxRNXxn4ILyH3kx8lrch7kD5fp/7KifDjAFFJ0DK2e2xsxLUToh2I
-PdMuzCUv4LL8kRsQ/+mUJY4YlOV9OKVAZbI/gPw9NnzBUBugz+wy0OG/nyS5k7G5
-MIzZm8yx3RieTNhwmw25NDLyGApHGQYsQ7DM/daA/wwdjFsyncxHSg==
------END RSA PRIVATE KEY-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA2e36r+Iguu9/DrzgvzjRZoTlZjDXtkqxrZPuyiL/zHezCSSq
+SMoMHPfRHy95ulhY70kPAgATvRpyyHqAekc1+V/dOHT0MyHpDJBNuyeA+cM2FgJm
+LLvZnf//a42nWgNNcx78baXN9ALE6Dq0Wtkb+y8KLb3KKdmwQl12THYLwwaQrgjp
+d3EN+Oi2QZhA+Nau+z+0oJyPfIAj7ssjFQBU2RODZxVJaFREU3DDKOVcL7VxU+NQ
+ehTqrcoWRWPPVtUnjSpZvp/M7vv2lBeXPbrMcL0M4+/acwRWEvcTGc28ZQZlPdRm
+yxTVN9jvsKdI1+nNTH5bhr0Y3B0KSvoB0USXWQIDAQABAoIBAQCWWYbgHSPzlBOW
+eVyc0Hg3QGx7aisIStP2Kt9NeYP87oAISNFqUmq0+Yu+9iQHGbiRrVe7S45SopKa
+GVnWApcMKsUWlCl9tWFxF4VpH0HuDm2cFZ+kMR1b0ifHbf0NLsYaLEB+7Sr/s4Fh
+rk6LdsnFK5jcIdn9sX/W6WAaND69Ft45sMXuAKPeqwySm7t77nlIEp4lJy/F/rvS
+UUV77UVL/5GqC1HOP1kXXEJDxHtNrDPh0fF49Qo/6JPRmLIOia0yKUS5+8lKHh6V
+J7nWTucyvviKoYvdJHpi9w2+DTI7mT33Go1Ss4QgHihzcURTtfjsn7yOelS6z8XM
+YOxURwoZAoGBAPZpod0Zpjm9trf547reFPLfAJUiKDj1wYXnlpSFAzOf7pSXDrfW
+L5dc3c5x2DiBXUlBu03ZWo7wVOKv7d9ByTrvXEqT1/KTqmHoI6Mzy0rn9wU0FmL4
+ASPcpPMnNStOSmRfe9HITDVvgLYo2wQEu1MXf5rGs5HjTRpEGVdWRkRPAoGBAOJo
+pQ9uRtaLlCF3KXoRr8HCBKI64IWDki7pbbLTglOBbmptQLfE2GPc/FPBsQwR3KBX
+m18A2pc7CRUKzsDavb7SyXTqtP3NF/AguTjGXIv/45QD6A96SStaYR0ZXSA3/uhZ
+rAaSSXxaI3mDTc32pXoXJDp7K+CPc5w00I8u1/fXAoGAe1UPoPyPiGL+K0M1yngR
+gCZBwmMgQrIutHjfk2Kn4ZTw8wpQYY8grt/aXNP6Zv3I1TvDJgneG6EKu5NWueHR
+eGAJj4JEGbPzGaH5BFyOKeXEa6RQeCStXWe4X8OGBzDeZzKrZKqeCjjO8V2tkWtU
+3xfp1GwTwLdGBhmDnYUfEl0CgYEA1vzVF6T4gQtDGtADQ5V91je8nKvZvQ4lhoRD
+lVZAX7j8tvSNSrMRYypZM9Mtoi9n1524vGqcJpR5WFDN6NUM7iFMCMhCGupgO7Vn
+DCFXidzvJgLbna7Zwd/tbWtDQa/KTqmvrwHD49/X5a+n9tapZRiKXznMfUzaU87W
+589sZjsCgYBQvtZpNQRqTDNOSuIJGsloMNp1HG2Keoj/nBQ9idkw29rUqhae7ZAa
+Csk91ix+AMR8nzQSkpHWOk2+RjdVAQ7m87bnQM6mmAJTcipikDfFxNqOWt7JkM2h
+Ydk36LHBIBVLOeqMXrhCHvai3h6efUz7wjGhNxRJ2OGrzWFfLgVsqA==
+-----END RSA PRIVATE KEY-----
diff --git a/test/mitmproxy/net/data/verificationcerts/trusted-root.srl b/test/mitmproxy/net/data/verificationcerts/trusted-root.srl
index cf60e2a5..ee465f9d 100644
--- a/test/mitmproxy/net/data/verificationcerts/trusted-root.srl
+++ b/test/mitmproxy/net/data/verificationcerts/trusted-root.srl
@@ -1 +1 @@
-A3E83F6857295BF7
+A3E83F6857295BF8
diff --git a/test/mitmproxy/net/http/http1/test_assemble.py b/test/mitmproxy/net/http/http1/test_assemble.py
index ab177885..4b4ab414 100644
--- a/test/mitmproxy/net/http/http1/test_assemble.py
+++ b/test/mitmproxy/net/http/http1/test_assemble.py
@@ -15,7 +15,6 @@ def test_assemble_request():
b"GET /path HTTP/1.1\r\n"
b"header: qvalue\r\n"
b"content-length: 7\r\n"
- b"host: address:22\r\n"
b"\r\n"
b"content"
)
@@ -84,17 +83,6 @@ def test_assemble_request_headers():
assert b"Transfer-Encoding" in c
-def test_assemble_request_headers_host_header():
- r = treq()
- r.headers = Headers()
- c = _assemble_request_headers(r.data)
- assert b"host" in c
-
- r.host = None
- c = _assemble_request_headers(r.data)
- assert b"host" not in c
-
-
def test_assemble_response_headers():
# https://github.com/mitmproxy/mitmproxy/issues/186
r = tresp(content=b"")
diff --git a/test/mitmproxy/net/http/http1/test_read.py b/test/mitmproxy/net/http/http1/test_read.py
index 4084c360..127f75ba 100644
--- a/test/mitmproxy/net/http/http1/test_read.py
+++ b/test/mitmproxy/net/http/http1/test_read.py
@@ -163,6 +163,11 @@ def test_expected_http_body_size():
assert expected_http_body_size(
treq(headers=Headers(expect="100-continue", content_length="42"))
) == 0
+ # Expect: 100-continue
+ assert expected_http_body_size(
+ treq(headers=Headers(expect="100-continue", content_length="42")),
+ expect_continue_as_0=False
+ ) == 42
# http://tools.ietf.org/html/rfc7230#section-3.3
assert expected_http_body_size(
diff --git a/test/mitmproxy/net/http/test_headers.py b/test/mitmproxy/net/http/test_headers.py
index 8fc8b027..5f208dcb 100644
--- a/test/mitmproxy/net/http/test_headers.py
+++ b/test/mitmproxy/net/http/test_headers.py
@@ -88,6 +88,8 @@ class TestHeaders:
headers = Headers(Host="foobarfoo.com", Accept="foo/bar")
replacements = headers.replace("foo", "bar", count=1)
assert replacements == 1
+ assert headers["Host"] == "barbarfoo.com"
+ assert headers["Accept"] == "foo/bar"
def test_parse_content_type():
diff --git a/test/mitmproxy/net/test_check.py b/test/mitmproxy/net/test_check.py
index 0ffd6b2e..649e71da 100644
--- a/test/mitmproxy/net/test_check.py
+++ b/test/mitmproxy/net/test_check.py
@@ -12,3 +12,61 @@ def test_is_valid_host():
# Allow underscore
assert check.is_valid_host(b"one_two")
assert check.is_valid_host(b"::1")
+
+ # IP Address Validations
+ assert check.is_valid_host(b'127.0.0.1')
+ assert check.is_valid_host(b'2001:0db8:85a3:0000:0000:8a2e:0370:7334')
+ assert check.is_valid_host(b'2001:db8:85a3:0:0:8a2e:370:7334')
+ assert check.is_valid_host(b'2001:db8:85a3::8a2e:370:7334')
+ assert not check.is_valid_host(b'2001:db8::85a3::7334')
+ assert check.is_valid_host(b'2001-db8-85a3-8d3-1319-8a2e-370-7348.ipv6-literal.net')
+
+ # TLD must be between 2 and 63 chars
+ assert check.is_valid_host(b'example.tl')
+ assert check.is_valid_host(b'example.tld')
+ assert check.is_valid_host(b'example.' + b"x" * 63)
+ assert not check.is_valid_host(b'example.' + b"x" * 64)
+
+ # misc characters test
+ assert not check.is_valid_host(b'ex@mple')
+ assert not check.is_valid_host(b'ex@mple.com')
+ assert not check.is_valid_host(b'example..com')
+ assert not check.is_valid_host(b'.example.com')
+ assert not check.is_valid_host(b'@.example.com')
+ assert not check.is_valid_host(b'!.example.com')
+
+ # Every label must be between 1 and 63 chars
+ assert not check.is_valid_host(b'.tld')
+ assert check.is_valid_host(b'x' * 1 + b'.tld')
+ assert check.is_valid_host(b'x' * 30 + b'.tld')
+ assert not check.is_valid_host(b'x' * 64 + b'.tld')
+ assert check.is_valid_host(b'x' * 1 + b'.example.tld')
+ assert check.is_valid_host(b'x' * 30 + b'.example.tld')
+ assert not check.is_valid_host(b'x' * 64 + b'.example.tld')
+
+ # Misc Underscore Test Cases
+ assert check.is_valid_host(b'_example')
+ assert check.is_valid_host(b'_example_')
+ assert check.is_valid_host(b'example_')
+ assert check.is_valid_host(b'_a.example.tld')
+ assert check.is_valid_host(b'a_.example.tld')
+ assert check.is_valid_host(b'_a_.example.tld')
+
+ # Misc Dash/Hyphen/Minus Test Cases
+ assert check.is_valid_host(b'-example')
+ assert check.is_valid_host(b'-example_')
+ assert check.is_valid_host(b'example-')
+ assert check.is_valid_host(b'-a.example.tld')
+ assert check.is_valid_host(b'a-.example.tld')
+ assert check.is_valid_host(b'-a-.example.tld')
+
+ # Misc Combo Test Cases
+ assert check.is_valid_host(b'api-.example.com')
+ assert check.is_valid_host(b'__a.example-site.com')
+ assert check.is_valid_host(b'_-a.example-site.com')
+ assert check.is_valid_host(b'_a_.example-site.com')
+ assert check.is_valid_host(b'-a-.example-site.com')
+ assert check.is_valid_host(b'api-.a.example.com')
+ assert check.is_valid_host(b'api-._a.example.com')
+ assert check.is_valid_host(b'api-.a_.example.com')
+ assert check.is_valid_host(b'api-.ab.example.com') \ No newline at end of file
diff --git a/test/mitmproxy/net/test_tcp.py b/test/mitmproxy/net/test_tcp.py
index 22a306dc..ba9b7ebc 100644
--- a/test/mitmproxy/net/test_tcp.py
+++ b/test/mitmproxy/net/test_tcp.py
@@ -37,7 +37,7 @@ class ClientCipherListHandler(tcp.BaseHandler):
sni = None
def handle(self):
- self.wfile.write(str(self.connection.get_cipher_list()).encode())
+ self.wfile.write(f"{self.connection.get_cipher_list()}\n".encode())
self.wfile.flush()
@@ -219,7 +219,7 @@ class TestInvalidTrustFile(tservers.ServerTestBase):
c.convert_to_tls(
sni="example.mitmproxy.org",
verify=SSL.VERIFY_PEER,
- ca_pemfile=tdata.path("mitmproxy/net/data/verificationcerts/generate.py")
+ ca_pemfile=cdata.path("data/verificationcerts/generate.py")
)
@@ -265,7 +265,7 @@ class TestSSLUpstreamCertVerificationWBadServerCert(tservers.ServerTestBase):
c.convert_to_tls(
sni="example.mitmproxy.org",
verify=SSL.VERIFY_PEER,
- ca_pemfile=tdata.path("mitmproxy/net/data/verificationcerts/trusted-root.crt")
+ ca_pemfile=cdata.path("data/verificationcerts/trusted-root.crt")
)
assert c.ssl_verification_error
@@ -289,7 +289,7 @@ class TestSSLUpstreamCertVerificationWBadHostname(tservers.ServerTestBase):
with pytest.raises(exceptions.TlsException):
c.convert_to_tls(
verify=SSL.VERIFY_PEER,
- ca_pemfile=tdata.path("mitmproxy/net/data/verificationcerts/trusted-root.crt")
+ ca_pemfile=cdata.path("data/verificationcerts/trusted-root.crt")
)
def test_mode_none_should_pass_without_sni(self, tdata):
@@ -297,10 +297,10 @@ class TestSSLUpstreamCertVerificationWBadHostname(tservers.ServerTestBase):
with c.connect():
c.convert_to_tls(
verify=SSL.VERIFY_NONE,
- ca_path=tdata.path("mitmproxy/net/data/verificationcerts/")
+ ca_path=cdata.path("data/verificationcerts/")
)
- assert "'no-hostname' doesn't match" in str(c.ssl_verification_error)
+ assert "Cannot validate hostname, SNI missing." in str(c.ssl_verification_error)
def test_should_fail(self, tdata):
c = tcp.TCPClient(("127.0.0.1", self.port))
@@ -309,7 +309,7 @@ class TestSSLUpstreamCertVerificationWBadHostname(tservers.ServerTestBase):
c.convert_to_tls(
sni="mitmproxy.org",
verify=SSL.VERIFY_PEER,
- ca_pemfile=tdata.path("mitmproxy/net/data/verificationcerts/trusted-root.crt")
+ ca_pemfile=cdata.path("data/verificationcerts/trusted-root.crt")
)
assert c.ssl_verification_error
@@ -328,7 +328,7 @@ class TestSSLUpstreamCertVerificationWValidCertChain(tservers.ServerTestBase):
c.convert_to_tls(
sni="example.mitmproxy.org",
verify=SSL.VERIFY_PEER,
- ca_pemfile=tdata.path("mitmproxy/net/data/verificationcerts/trusted-root.crt")
+ ca_pemfile=cdata.path("data/verificationcerts/trusted-root.crt")
)
assert c.ssl_verification_error is None
@@ -344,7 +344,7 @@ class TestSSLUpstreamCertVerificationWValidCertChain(tservers.ServerTestBase):
c.convert_to_tls(
sni="example.mitmproxy.org",
verify=SSL.VERIFY_PEER,
- ca_path=tdata.path("mitmproxy/net/data/verificationcerts/")
+ ca_path=cdata.path("data/verificationcerts/")
)
assert c.ssl_verification_error is None
@@ -376,14 +376,14 @@ class TestSSLClientCert(tservers.ServerTestBase):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
c.convert_to_tls(
- cert=tdata.path("mitmproxy/net/data/clientcert/client.pem"))
+ cert=cdata.path("data/clientcert/client.pem"))
assert c.rfile.readline().strip() == b"1"
def test_clientcert_err(self, tdata):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
with pytest.raises(exceptions.TlsException):
- c.convert_to_tls(cert=tdata.path("mitmproxy/net/data/clientcert/make"))
+ c.convert_to_tls(cert=cdata.path("data/clientcert/make"))
class TestSNI(tservers.ServerTestBase):
@@ -421,16 +421,18 @@ class TestServerCipherList(tservers.ServerTestBase):
cipher_list='AES256-GCM-SHA384'
)
+ @pytest.mark.xfail
def test_echo(self):
+ # Not working for OpenSSL 1.1.1, see
+ # https://github.com/pyca/pyopenssl/blob/fc802df5c10f0d1cd9749c94887d652fa26db6fb/src/OpenSSL/SSL.py#L1192-L1196
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
c.convert_to_tls(sni="foo.com")
- expected = b"['AES256-GCM-SHA384']"
- assert c.rfile.read(len(expected) + 2) == expected
+ expected = b"['TLS_AES_256_GCM_SHA384']"
+ assert c.rfile.readline() == expected
class TestServerCurrentCipher(tservers.ServerTestBase):
-
class handler(tcp.BaseHandler):
sni = None
@@ -442,7 +444,10 @@ class TestServerCurrentCipher(tservers.ServerTestBase):
cipher_list='AES256-GCM-SHA384'
)
+ @pytest.mark.xfail
def test_echo(self):
+ # Not working for OpenSSL 1.1.1, see
+ # https://github.com/pyca/pyopenssl/blob/fc802df5c10f0d1cd9749c94887d652fa26db6fb/src/OpenSSL/SSL.py#L1192-L1196
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
c.convert_to_tls(sni="foo.com")
@@ -608,7 +613,7 @@ class TestDHParams(tservers.ServerTestBase):
def test_dhparams(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_tls()
+ c.convert_to_tls(method=SSL.TLSv1_2_METHOD)
ret = c.get_current_cipher()
assert ret[0] == "DHE-RSA-AES256-SHA"
diff --git a/test/mitmproxy/net/test_tls.py b/test/mitmproxy/net/test_tls.py
index c4e76bc6..e78564c7 100644
--- a/test/mitmproxy/net/test_tls.py
+++ b/test/mitmproxy/net/test_tls.py
@@ -43,7 +43,7 @@ class TestMasterSecretLogger(tservers.ServerTestBase):
tls.log_master_secret.close()
with open(logfile, "rb") as f:
- assert f.read().count(b"CLIENT_RANDOM") == 2
+ assert f.read().count(b"CLIENT_RANDOM") >= 2
tls.log_master_secret = _logfun
diff --git a/test/mitmproxy/proxy/protocol/test_http2.py b/test/mitmproxy/proxy/protocol/test_http2.py
index b5f21413..0afa6305 100644
--- a/test/mitmproxy/proxy/protocol/test_http2.py
+++ b/test/mitmproxy/proxy/protocol/test_http2.py
@@ -496,6 +496,71 @@ class TestStreamResetFromServer(_Http2Test):
assert self.master.state.flows[0].response is None
+class TestAllStreamResetsFromServer(_Http2Test):
+
+ current_error_name = None
+ current_error_code = None
+
+ @classmethod
+ def handle_server_event(cls, event, h2_conn, rfile, wfile):
+ if isinstance(event, h2.events.ConnectionTerminated):
+ return False
+ elif isinstance(event, h2.events.RequestReceived):
+ h2_conn.reset_stream(event.stream_id, int(cls.current_error_code))
+ wfile.write(h2_conn.data_to_send())
+ wfile.flush()
+ return True
+
+ def test_all_stream_reset_error_codes(self):
+ for error_name, error_code in h2.errors.ErrorCodes.__members__.items():
+ self.__class__.current_error_name = error_name
+ self.__class__.current_error_code = error_code
+ try:
+ self.run_test_for_stream_reset()
+ except:
+ print('Exception occurred during test for error code {} ({})'.format(
+ error_name, error_code
+ ))
+ raise
+
+ def run_test_for_stream_reset(self):
+ h2_conn = self.setup_connection()
+
+ self._send_request(
+ self.client.wfile,
+ h2_conn,
+ headers=[
+ (':authority', "127.0.0.1:{}".format(self.server.server.address[1])),
+ (':method', 'GET'),
+ (':scheme', 'https'),
+ (':path', '/'),
+ ],
+ )
+
+ self.client.rfile.o.settimeout(1)
+
+ done = False
+ while not done:
+ try:
+ raw = b''.join(http2.read_raw_frame(self.client.rfile))
+ events = h2_conn.receive_data(raw)
+ except exceptions.HttpException:
+ print(traceback.format_exc())
+ assert False
+
+ self.client.wfile.write(h2_conn.data_to_send())
+ self.client.wfile.flush()
+
+ for event in events:
+ if isinstance(event, h2.events.StreamReset):
+ assert event.error_code == int(self.current_error_code)
+ done = True
+
+ h2_conn.close_connection()
+ self.client.wfile.write(h2_conn.data_to_send())
+ self.client.wfile.flush()
+
+
class TestBodySizeLimit(_Http2Test):
@classmethod
diff --git a/test/mitmproxy/test_command.py b/test/mitmproxy/test_command.py
index a432f9e3..7158935a 100644
--- a/test/mitmproxy/test_command.py
+++ b/test/mitmproxy/test_command.py
@@ -512,9 +512,15 @@ class TAttr:
raise IOError
+class TAttr2:
+ def __getattr__(self, item):
+ return TAttr2()
+
+
class TCmds(TAttr):
def __init__(self):
self.TAttr = TAttr()
+ self.TAttr2 = TAttr2()
@command.command("empty")
def empty(self) -> None:
@@ -524,7 +530,8 @@ class TCmds(TAttr):
@pytest.mark.asyncio
async def test_collect_commands():
"""
- This tests for the error thrown by hasattr()
+ This tests for errors thrown by getattr() or __getattr__ implementations
+ that return an object for .command_name.
"""
with taddons.context() as tctx:
c = command.CommandManager(tctx.master)
diff --git a/test/mitmproxy/test_connections.py b/test/mitmproxy/test_connections.py
index 7c371c1e..ae296631 100644
--- a/test/mitmproxy/test_connections.py
+++ b/test/mitmproxy/test_connections.py
@@ -199,6 +199,9 @@ class TestClientConnectionTLS:
s = socket.create_connection(address)
s = ctx.wrap_socket(s, server_hostname=sni)
s.send(b'foobar')
+ # we need to wait for the test to finish successfully before calling .close() on Windows.
+ # The workaround here is to signal completion by sending data the other way around.
+ s.recv(3)
s.close()
threading.Thread(target=client_run).start()
@@ -216,6 +219,7 @@ class TestClientConnectionTLS:
assert c.sni == sni
assert c.tls_established
assert c.rfile.read(6) == b'foobar'
+ c.wfile.send(b"foo")
c.finish()
sock.close()
diff --git a/test/mitmproxy/tools/console/test_keymap.py b/test/mitmproxy/tools/console/test_keymap.py
index 0d6f9e88..f2c97ded 100644
--- a/test/mitmproxy/tools/console/test_keymap.py
+++ b/test/mitmproxy/tools/console/test_keymap.py
@@ -117,6 +117,21 @@ def test_load_path(tmpdir):
kmc.load_path(km, dst)
assert(km.get("chooser", "key1"))
+ with open(dst, 'w') as f:
+ f.write(
+ """
+ - key: key2
+ ctx: [flowlist]
+ cmd: foo
+ - key: key2
+ ctx: [flowview]
+ cmd: bar
+ """
+ )
+ kmc.load_path(km, dst)
+ assert(km.get("flowlist", "key2"))
+ assert(km.get("flowview", "key2"))
+
km.add("key123", "str", ["flowlist", "flowview"])
with open(dst, 'w') as f:
f.write(
@@ -127,10 +142,9 @@ def test_load_path(tmpdir):
"""
)
kmc.load_path(km, dst)
- for b in km.bindings:
- if b.key == "key123":
- assert b.contexts == ["options"]
- break
+ assert(km.get("flowlist", "key123"))
+ assert(km.get("flowview", "key123"))
+ assert(km.get("options", "key123"))
def test_parse():
diff --git a/tox.ini b/tox.ini
index 42da5706..6ae38cbb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -52,7 +52,7 @@ passenv = CI_* GITHUB_* AWS_* TWINE_* DOCKER_*
deps =
-rrequirements.txt
pyinstaller==3.5
- twine==2.0.0
+ twine==3.1.1
awscli
commands =
mitmdump --version
diff --git a/web/README.md b/web/README.md
index c43d09f0..ae558bd6 100644
--- a/web/README.md
+++ b/web/README.md
@@ -1,6 +1,8 @@
# Quick Start
+**Be sure to follow the Development Setup instructions found in the README.md,
+and activate your virtualenv environment before proceeding.**
- Run `yarn` to install dependencies
-- Run `gulp` to start live-compilation.
+- Run `yarn run gulp` to start live-compilation.
- Run `mitmweb` and open http://localhost:8081/
diff --git a/web/src/js/__tests__/components/Header/__snapshots__/OptionMenuSpec.js.snap b/web/src/js/__tests__/components/Header/__snapshots__/OptionMenuSpec.js.snap
index 83d9355c..106f1732 100644
--- a/web/src/js/__tests__/components/Header/__snapshots__/OptionMenuSpec.js.snap
+++ b/web/src/js/__tests__/components/Header/__snapshots__/OptionMenuSpec.js.snap
@@ -76,7 +76,7 @@ exports[`OptionMenu Component should render correctly 1`] = `
onChange={[Function]}
type="checkbox"
/>
- Verify server certificates
+ Don't verify server certificates
</label>
</div>
</div>
diff --git a/web/src/js/components/Header/OptionMenu.jsx b/web/src/js/components/Header/OptionMenu.jsx
index 765129ed..4eb319ec 100644
--- a/web/src/js/components/Header/OptionMenu.jsx
+++ b/web/src/js/components/Header/OptionMenu.jsx
@@ -31,7 +31,7 @@ function OptionMenu({ openOptions }) {
Use host header for display
</SettingsToggle>
<SettingsToggle setting="ssl_insecure">
- Verify server certificates
+ Don't verify server certificates
</SettingsToggle>
</div>
<div className="menu-legend">Quick Options</div>