diff options
109 files changed, 9490 insertions, 0 deletions
diff --git a/pathod/.appveyor.yml b/pathod/.appveyor.yml new file mode 100644 index 00000000..dbb6d2fa --- /dev/null +++ b/pathod/.appveyor.yml @@ -0,0 +1,11 @@ +version: '{build}' +shallow_clone: true +environment: + matrix: + - PYTHON: "C:\\Python27" +install: + - "%PYTHON%\\Scripts\\pip install --src . -r requirements.txt" + - "%PYTHON%\\python -c \"from OpenSSL import SSL; print(SSL.SSLeay_version(SSL.SSLEAY_VERSION))\"" +build: off # Not a C# project +test_script: + - "%PYTHON%\\Scripts\\py.test -n 4"
\ No newline at end of file diff --git a/pathod/.coveragerc b/pathod/.coveragerc new file mode 100644 index 00000000..7e978294 --- /dev/null +++ b/pathod/.coveragerc @@ -0,0 +1,10 @@ +[run] +branch = True + +[report] +show_missing = True +include = *libpathod* +exclude_lines = + pragma: nocover + pragma: no cover + raise NotImplementedError() diff --git a/pathod/.env b/pathod/.env new file mode 100644 index 00000000..69ac3f05 --- /dev/null +++ b/pathod/.env @@ -0,0 +1,6 @@ +DIR="$( dirname "${BASH_SOURCE[0]}" )" +ACTIVATE_DIR="$(if [ -f "$DIR/../venv.mitmproxy/bin/activate" ]; then echo 'bin'; else echo 'Scripts'; fi;)" +if [ -z "$VIRTUAL_ENV" ] && [ -f "$DIR/../venv.mitmproxy/$ACTIVATE_DIR/activate" ]; then + echo "Activating mitmproxy virtualenv..." + source "$DIR/../venv.mitmproxy/$ACTIVATE_DIR/activate" +fi diff --git a/pathod/.gitignore b/pathod/.gitignore new file mode 100644 index 00000000..7eeeb614 --- /dev/null +++ b/pathod/.gitignore @@ -0,0 +1,15 @@ +# Python object files +*.py[cd] +MANIFEST +/build +/dist +# Vim swap files +*.swp +/doc +.coverage +.noseids +netlib +venv +.idea/ +pathod.egg-info/ +.cache/
\ No newline at end of file diff --git a/pathod/.jsbeautifyrc b/pathod/.jsbeautifyrc new file mode 100644 index 00000000..725c15ad --- /dev/null +++ b/pathod/.jsbeautifyrc @@ -0,0 +1,22 @@ +{ + "indent_size": 4, + "indent_char": " ", + "eol": "\n", + "indent_level": 0, + "indent_with_tabs": false, + "preserve_newlines": true, + "max_preserve_newlines": 10, + "jslint_happy": false, + "space_after_anon_function": false, + "brace_style": "collapse", + "keep_array_indentation": false, + "keep_function_indentation": false, + "space_before_conditional": true, + "break_chained_methods": false, + "eval_code": false, + "unescape_strings": false, + "wrap_line_length": 80, + "wrap_attributes": "auto", + "wrap_attributes_indent_size": 4, + "end_with_newline": true +} diff --git a/pathod/.landscape.yml b/pathod/.landscape.yml new file mode 100644 index 00000000..9a3b615f --- /dev/null +++ b/pathod/.landscape.yml @@ -0,0 +1,16 @@ +max-line-length: 120 +pylint: + options: + dummy-variables-rgx: _$|.+_$|dummy_.+ + + disable: + - missing-docstring + - protected-access + - too-few-public-methods + - too-many-arguments + - too-many-instance-attributes + - too-many-locals + - too-many-public-methods + - too-many-return-statements + - too-many-statements + - unpacking-non-sequence
\ No newline at end of file diff --git a/pathod/.sources/bootswatch.less b/pathod/.sources/bootswatch.less new file mode 100644 index 00000000..f9e4b827 --- /dev/null +++ b/pathod/.sources/bootswatch.less @@ -0,0 +1,171 @@ +// Bootswatch.less +// Swatch: Journal +// Version: 2.0.4 +// ----------------------------------------------------- + +// TYPOGRAPHY +// ----------------------------------------------------- + +@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700'); + +h1, h2, h3, h4, h5, h6, .navbar .brand { + font-weight: 700; +} + +// SCAFFOLDING +// ----------------------------------------------------- + +a { + text-decoration: none; +} + +.nav a, .navbar .brand, .subnav a, a.btn, .dropdown-menu a { + text-decoration: none; +} + +// NAVBAR +// ----------------------------------------------------- + +.navbar { + + .navbar-inner { + @shadow: 0 2px 4px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1); + .box-shadow(@shadow); + border-top: 1px solid #E5E5E5; + .border-radius(0); + } + + .brand { + text-shadow: none; + + &:hover { + background-color: #EEEEEE; + } + } + + .navbar-text { + line-height: 68px; + } + + .nav > li > a { + text-shadow: none; + } + + .dropdown-menu { + .border-radius(0); + } + + .nav li.dropdown.active > .dropdown-toggle, + .nav li.dropdown.active > .dropdown-toggle:hover, + .nav li.dropdown.open > .dropdown-toggle, + .nav li.dropdown.active.open > .dropdown-toggle, + .nav li.dropdown.active.open > .dropdown-toggle:hover { + background-color: @grayLighter; + color: @linkColor; + } + + .nav li.dropdown .dropdown-toggle .caret, + .nav .open .caret, + .nav .open .dropdown-toggle:hover .caret { + border-top-color: @black; + opacity: 1; + } + + .nav-collapse.in .nav li > a:hover { + background-color: @grayLighter; + } + + .nav-collapse .nav li > a { + color: @textColor; + text-decoration: none; + font-weight: normal; + } + + .nav-collapse .navbar-form, + .nav-collapse .navbar-search { + border-color: transparent; + } + + .navbar-search .search-query, + .navbar-search .search-query:hover { + border: 1px solid @grayLighter; + color: @textColor; + .placeholder(@gray); + } +} + +div.subnav { + background-color: @bodyBackground; + background-image: none; + @shadow: 0 1px 2px rgba(0,0,0,.25); + .box-shadow(@shadow); + .border-radius(0); + + &.subnav-fixed { + top: @navbarHeight; + } + + .nav > li > a:hover, + .nav > .active > a, + .nav > .active > a:hover { + color: @textColor; + text-decoration: none; + font-weight: normal; + } + + .nav > li:first-child > a, + .nav > li:first-child > a:hover { + .border-radius(0); + } +} + +// BUTTONS +// ----------------------------------------------------- + +.btn-primary { + .buttonBackground(lighten(@linkColor, 5%), @linkColor); +} + +[class^="icon-"], [class*=" icon-"] { + vertical-align: -2px; +} + +// MODALS +// ----------------------------------------------------- + +.modal { + .border-radius(0px); + background: @bodyBackground; +} + +.modal-header { + border-bottom: none; +} + +.modal-header .close { + text-decoration: none; +} + +.modal-footer { + background: transparent; + .box-shadow(none); + border-top: none; +} + + +// MISC +// ----------------------------------------------------- + +code, pre, pre.prettyprint, .well { + background-color: @grayLighter; +} + +.hero-unit { + .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); + border: 1px solid rgba(0,0,0,.05); + .border-radius(0); +} + +.table-bordered, .well, .prettyprint { + .border-radius(0); +} diff --git a/pathod/.sources/make b/pathod/.sources/make new file mode 100755 index 00000000..1c8b1d69 --- /dev/null +++ b/pathod/.sources/make @@ -0,0 +1,5 @@ +#!/bin/sh +pygmentize -f html ../examples/test_context.py > ../libpathod/templates/examples_context.html +pygmentize -f html ../examples/test_setup.py > ../libpathod/templates/examples_setup.html +pygmentize -f html ../examples/test_setupall.py > ../libpathod/templates/examples_setupall.html +pygmentize -f html ../examples/libpathod_pathoc.py > ../libpathod/templates/libpathod_pathoc.html diff --git a/pathod/.sources/variables.less b/pathod/.sources/variables.less new file mode 100644 index 00000000..75ff5be6 --- /dev/null +++ b/pathod/.sources/variables.less @@ -0,0 +1,208 @@ +// Variables.less +// Variables to customize the look and feel of Bootstrap +// Swatch: Journal +// Version: 2.0.4 +// ----------------------------------------------------- + +// GLOBAL VALUES +// -------------------------------------------------- + + +// Grays +// ------------------------- +@black: #000; +@grayDarker: #222; +@grayDark: #333; +@gray: #888; +@grayLight: #999; +@grayLighter: #eee; +@white: #fff; + + +// Accent colors +// ------------------------- +@blue: #4380D3; +@blueDark: darken(@blue, 15%); +@green: #22B24C; +@red: #C00; +@yellow: #FCFADB; +@orange: #FF7F00; +@pink: #CC99CC; +@purple: #7a43b6; +@tan: #FFCA73; + + + +// Scaffolding +// ------------------------- +@bodyBackground: #FCFBFD; +@textColor: @grayDarker; + + +// Links +// ------------------------- +@linkColor: @blue; +@linkColorHover: @red; + + +// Typography +// ------------------------- +@sansFontFamily: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif; +@serifFontFamily: Georgia, "Times New Roman", Times, serif; +@monoFontFamily: Menlo, Monaco, Consolas, "Courier New", monospace; + +@baseFontSize: 14px; +@baseFontFamily: @sansFontFamily; +@baseLineHeight: 18px; +@altFontFamily: @serifFontFamily; + +@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily +@headingsFontWeight: bold; // instead of browser default, bold +@headingsColor: inherit; // empty to use BS default, @textColor + + +// Tables +// ------------------------- +@tableBackground: transparent; // overall background-color +@tableBackgroundAccent: @grayLighter; // for striping +@tableBackgroundHover: #f5f5f5; // for hover +@tableBorder: #ddd; // table and cell border + + +// Buttons +// ------------------------- +@btnBackground: @white; +@btnBackgroundHighlight: darken(@white, 10%); +@btnBorder: darken(@white, 20%); + +@btnPrimaryBackground: @linkColor; +@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 15%); + +@btnInfoBackground: #5bc0de; +@btnInfoBackgroundHighlight: #2f96b4; + +@btnSuccessBackground: #62c462; +@btnSuccessBackgroundHighlight: #51a351; + +@btnWarningBackground: lighten(@orange, 10%); +@btnWarningBackgroundHighlight: @orange; + +@btnDangerBackground: #ee5f5b; +@btnDangerBackgroundHighlight: #bd362f; + +@btnInverseBackground: @linkColor; +@btnInverseBackgroundHighlight: darken(@linkColor, 5%); + + +// Forms +// ------------------------- +@inputBackground: @white; +@inputBorder: #ccc; +@inputBorderRadius: 3px; +@inputDisabledBackground: @grayLighter; +@formActionsBackground: @grayLighter; + +// Dropdowns +// ------------------------- +@dropdownBackground: @bodyBackground; +@dropdownBorder: rgba(0,0,0,.2); +@dropdownLinkColor: @textColor; +@dropdownLinkColorHover: @textColor; +@dropdownLinkBackgroundHover: #eee; +@dropdownDividerTop: #e5e5e5; +@dropdownDividerBottom: @white; + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +@zindexDropdown: 1000; +@zindexPopover: 1010; +@zindexTooltip: 1020; +@zindexFixedNavbar: 1030; +@zindexModalBackdrop: 1040; +@zindexModal: 1050; + + +// Sprite icons path +// ------------------------- +@iconSpritePath: "../img/glyphicons-halflings.png"; +@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png"; + + +// Input placeholder text color +// ------------------------- +@placeholderText: @grayLight; + + +// Hr border color +// ------------------------- +@hrBorder: @grayLighter; + + +// Navbar +// ------------------------- +@navbarHeight: 50px; +@navbarBackground: @bodyBackground; +@navbarBackgroundHighlight: @bodyBackground; + +@navbarText: @textColor; +@navbarLinkColor: @linkColor; +@navbarLinkColorHover: @linkColor; +@navbarLinkColorActive: @navbarLinkColorHover; +@navbarLinkBackgroundHover: @grayLighter; +@navbarLinkBackgroundActive: @grayLighter; + +@navbarSearchBackground: lighten(@navbarBackground, 25%); +@navbarSearchBackgroundFocus: @white; +@navbarSearchBorder: darken(@navbarSearchBackground, 30%); +@navbarSearchPlaceholderColor: #ccc; +@navbarBrandColor: @blue; + + +// Hero unit +// ------------------------- +@heroUnitBackground: @grayLighter; +@heroUnitHeadingColor: inherit; +@heroUnitLeadColor: inherit; + + +// Form states and alerts +// ------------------------- +@warningText: #c09853; +@warningBackground: #fcf8e3; +@warningBorder: darken(spin(@warningBackground, -10), 3%); + +@errorText: #b94a48; +@errorBackground: #f2dede; +@errorBorder: darken(spin(@errorBackground, -10), 3%); + +@successText: #468847; +@successBackground: #dff0d8; +@successBorder: darken(spin(@successBackground, -10), 5%); + +@infoText: #3a87ad; +@infoBackground: #d9edf7; +@infoBorder: darken(spin(@infoBackground, -10), 7%); + + + +// GRID +// -------------------------------------------------- + +// Default 940px grid +// ------------------------- +@gridColumns: 12; +@gridColumnWidth: 60px; +@gridGutterWidth: 20px; +@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// Fluid grid +// ------------------------- +@fluidGridColumnWidth: 6.382978723%; +@fluidGridGutterWidth: 2.127659574%; diff --git a/pathod/.travis.yml b/pathod/.travis.yml new file mode 100644 index 00000000..92634df7 --- /dev/null +++ b/pathod/.travis.yml @@ -0,0 +1,70 @@ +sudo: false +language: python + +matrix: + fast_finish: true + include: + - python: 2.7 + - python: 2.7 + env: OPENSSL=1.0.2 + addons: + apt: + sources: + # Debian sid currently holds OpenSSL 1.0.2 + # change this with future releases! + - debian-sid + packages: + - libssl-dev + - python: pypy + - python: pypy + env: OPENSSL=1.0.2 + addons: + apt: + sources: + # Debian sid currently holds OpenSSL 1.0.2 + # change this with future releases! + - debian-sid + packages: + - libssl-dev + allow_failures: + # We allow pypy to fail until Travis fixes their infrastructure to a pypy + # with a recent enought CFFI library to run cryptography 1.0+. + - python: pypy + +install: + - "pip install --src . -r requirements.txt" + +before_script: + - "openssl version -a" + +script: + - "py.test --cov libpathod -v" + +after_success: + - coveralls + +notifications: + irc: + channels: + - "irc.oftc.net#mitmproxy" + on_success: change + on_failure: always + slack: + rooms: + - mitmproxy:YaDGC9Gt9TEM7o8zkC2OLNsu#ci + on_success: always + on_failure: always + +# exclude cryptography from cache +# it depends on libssl-dev version +# which needs to be compiled specifically to each version +before_cache: + - pip uninstall -y cryptography + +cache: + directories: + - $HOME/.cache/pip + - /home/travis/virtualenv/python2.7.9/lib/python2.7/site-packages + - /home/travis/virtualenv/python2.7.9/bin + - /home/travis/virtualenv/pypy-2.5.0/site-packages + - /home/travis/virtualenv/pypy-2.5.0/bin diff --git a/pathod/CHANGELOG b/pathod/CHANGELOG new file mode 100644 index 00000000..2de445b4 --- /dev/null +++ b/pathod/CHANGELOG @@ -0,0 +1,83 @@ +7 November 2014: pathod 0.11: + + * Hugely improved SSL support, including dynamic generation of certificates + using the mitproxy cacert + * pathoc -S dumps information on the remote SSL certificate chain + * Big improvements to fuzzing, including random spec selection and memoization to avoid repeating randomly generated patterns + * Reflected patterns, allowing you to embed a pathod server response specification in a pathoc request, resolving both on client side. This makes fuzzing proxies and other intermediate systems much better. + + +25 August 2013: pathod 0.9.2: + + * Adapt to interface changes in netlib + + +15 May 2013: pathod 0.9 (version synced with mitmproxy): + + * Pathod proxy mode. You can now configure clients to use pathod as an + HTTP/S proxy. + + * Pathoc proxy support, including using CONNECT to tunnel directly to + targets. + + * Pathoc client certificate support. + + * API improvements, bugfixes. + + +16 November 2012: pathod 0.3: + + A release focusing on shoring up our fuzzing capabilities, especially with + pathoc. + + * pathoc -q and -r options, output full request and response text. + + * pathod -q and -r options, add full request and response text to pathod's + log buffer. + + * pathoc and pathod -x option, makes -q and -r options log in hex dump + format. + + * pathoc -C option, specify response codes to ignore. + + * pathoc -T option, instructs pathoc to ignore timeouts. + + * pathoc -o option, a one-shot mode that exits after the first non-ignored + response. + + * pathoc and pathod -e option, which explains the resulting message by + expanding random and generated portions, and logging a reproducible + specification. + + * Streamline the specification langauge. HTTP response message is now + specified using the "r" mnemonic. + + * Add a "u" mnemonic for specifying User-Agent strings. Add a set of + standard user-agent strings accessible through shortcuts. + + * Major internal refactoring and cleanup. + + * Many bugfixes. + + +22 August 2012: pathod 0.2: + + * Add pathoc, a pathological HTTP client. + + * Add libpathod.test, a truss for using pathod in unit tests. + + * Add an injection operator to the specification language. + + * Allow Python escape sequences in value literals. + + * Allow execution of requests and responses from file, using the new + operator. + + * Add daemonization to Pathod, and make it more robust for public-facing use. + + * Let pathod pick an arbitrary open port if -p 0 is specified. + + * Move from Tornado to netlib, the network library written for mitmproxy. + + * Move the web application to Flask. + + * Massively expand the documentation. diff --git a/pathod/CONTRIBUTORS b/pathod/CONTRIBUTORS new file mode 100644 index 00000000..ab76a35c --- /dev/null +++ b/pathod/CONTRIBUTORS @@ -0,0 +1,6 @@ + 426 Aldo Cortesi + 74 Maximilian Hils + 50 Thomas Kriechbaumer + 1 Felix Yan + 1 requires.io + 1 starenka diff --git a/pathod/LICENSE b/pathod/LICENSE new file mode 100644 index 00000000..908aa13f --- /dev/null +++ b/pathod/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2008, Aldo Cortesi. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pathod/MANIFEST.in b/pathod/MANIFEST.in new file mode 100644 index 00000000..f492b0ef --- /dev/null +++ b/pathod/MANIFEST.in @@ -0,0 +1,6 @@ +include LICENSE CHANGELOG README.txt +exclude README.mkd +recursive-include test * +recursive-include libpathod * +recursive-include examples * +recursive-exclude * *.pyc *.pyo *.swo *.swp
\ No newline at end of file diff --git a/pathod/README.mkd b/pathod/README.mkd new file mode 100644 index 00000000..9f8c487b --- /dev/null +++ b/pathod/README.mkd @@ -0,0 +1,44 @@ +[](https://travis-ci.org/mitmproxy/pathod) +[](https://landscape.io/github/mitmproxy/pathod/master) +[](https://coveralls.io/r/mitmproxy/pathod) +[](https://pypi.python.org/pypi/pathod) +[](https://pypi.python.org/pypi/pathod) +[](https://pypi.python.org/pypi/pathod) + +__pathod__ is a collection of pathological tools for testing and torturing HTTP +clients and servers. The project has three components: + +- __pathod__, an pathological HTTP daemon. +- __pathoc__, a perverse HTTP client. +- __libpathod.test__, an API for easily using __pathod__ and __pathoc__ in unit tests. + + +# Documentation + +The pathod documentation is self-hosted. Just fire up pathod, like so: + + ./pathod + +And then browse to: + + http://localhost:9999 + +You can always view the documentation for the latest release at the pathod +website: + + http://pathod.net + +# Installing + +If you already have __pip__ on your system, installing __pathod__ and its +dependencies is dead simple: + + pip install pathod + +The project has the following dependencies: + +* [netlib](https://github.com/mitmproxy/netlib) +* [requests](http://docs.python-requests.org/en/latest/index.html) + +The project's test suite uses the +[nose](http://nose.readthedocs.org/en/latest/) unit testing framework. diff --git a/pathod/README.txt b/pathod/README.txt new file mode 100644 index 00000000..f8fbdac5 --- /dev/null +++ b/pathod/README.txt @@ -0,0 +1,43 @@ +**pathod** is a collection of pathological tools for testing and torturing HTTP +clients and servers. The project has three components: + +- **pathod**, an pathological HTTP daemon. +- **pathoc**, a perverse HTTP client. +- **libpathod.test**, an API for easily using pathod and pathoc in unit tests. + + +Documentation +------------- + +The pathod documentation is self-hosted. Just fire up pathod, like so: + + ./pathod + +And then browse to: + + http://localhost:9999 + +You can always view the documentation for the latest release at the pathod +website: + + http://pathod.net + + +Installing +---------- + +If you already have **pip** on your system, installing **pathod** and its +dependencies is dead simple: + + pip install pathod + +The project has the following dependencies: + +* netlib_ +* requests_ + +The project's test suite uses the nose_ unit testing framework. + +.. _netlib: https://github.com/mitmproxy/netlib +.. _requests: http://docs.python-requests.org/en/latest/index.html +.. _nose: http://nose.readthedocs.org/en/latest/ diff --git a/pathod/examples/libpathod_pathoc.py b/pathod/examples/libpathod_pathoc.py new file mode 100644 index 00000000..cf94151b --- /dev/null +++ b/pathod/examples/libpathod_pathoc.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +from libpathod import pathoc + +p = pathoc.Pathoc(("google.com", 80)) +p.connect() +print p.request("get:/") +print p.request("get:/foo") diff --git a/pathod/examples/test_context.py b/pathod/examples/test_context.py new file mode 100644 index 00000000..7c0386c1 --- /dev/null +++ b/pathod/examples/test_context.py @@ -0,0 +1,23 @@ +import requests +from libpathod import test + + +def test_simple(): + """ + Testing the requests module with + a pathod context manager. + """ + # Start pathod in a separate thread + with test.Daemon() as d: + # Get a URL for a pathod spec + url = d.p("200:b@100") + # ... and request it + r = requests.put(url) + + # Check the returned data + assert r.status_code == 200 + assert len(r.content) == 100 + + # Check pathod's internal log + log = d.last_log()["request"] + assert log["method"] == "PUT" diff --git a/pathod/examples/test_setup.py b/pathod/examples/test_setup.py new file mode 100644 index 00000000..6085c98a --- /dev/null +++ b/pathod/examples/test_setup.py @@ -0,0 +1,31 @@ +import requests +from libpathod import test + + +class Test: + + """ + Testing the requests module with + a pathod instance started for + each test. + """ + + def setup(self): + self.d = test.Daemon() + + def teardown(self): + self.d.shutdown() + + def test_simple(self): + # Get a URL for a pathod spec + url = self.d.p("200:b@100") + # ... and request it + r = requests.put(url) + + # Check the returned data + assert r.status_code == 200 + assert len(r.content) == 100 + + # Check pathod's internal log + log = self.d.last_log()["request"] + assert log["method"] == "PUT" diff --git a/pathod/examples/test_setupall.py b/pathod/examples/test_setupall.py new file mode 100644 index 00000000..f0ba5844 --- /dev/null +++ b/pathod/examples/test_setupall.py @@ -0,0 +1,39 @@ +import requests +from libpathod import test + + +class Test: + + """ + Testing the requests module with + a single pathod instance started + for the test suite. + """ + @classmethod + def setup_class(cls): + cls.d = test.Daemon() + + @classmethod + def teardown_class(cls): + cls.d.shutdown() + + def setup(self): + # Clear the pathod logs between tests + self.d.clear_log() + + def test_simple(self): + # Get a URL for a pathod spec + url = self.d.p("200:b@100") + # ... and request it + r = requests.put(url) + + # Check the returned data + assert r.status_code == 200 + assert len(r.content) == 100 + + # Check pathod's internal log + log = self.d.last_log()["request"] + assert log["method"] == "PUT" + + def test_two(self): + assert not self.d.log() diff --git a/pathod/libpathod/__init__.py b/pathod/libpathod/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/pathod/libpathod/__init__.py diff --git a/pathod/libpathod/app.py b/pathod/libpathod/app.py new file mode 100644 index 00000000..debebaf2 --- /dev/null +++ b/pathod/libpathod/app.py @@ -0,0 +1,179 @@ +import logging +import pprint +import cStringIO +import copy +from flask import Flask, jsonify, render_template, request, abort, make_response +from . import version, language, utils +from netlib.http import user_agents + +logging.basicConfig(level="DEBUG") +EXAMPLE_HOST = "example.com" +EXAMPLE_WEBSOCKET_KEY = "examplekey" + +# pylint: disable=unused-variable + + +def make_app(noapi, debug): + app = Flask(__name__) + app.debug = debug + + if not noapi: + @app.route('/api/info') + def api_info(): + return jsonify( + version=version.IVERSION + ) + + @app.route('/api/log') + def api_log(): + return jsonify( + log=app.config["pathod"].get_log() + ) + + @app.route('/api/clear_log') + def api_clear_log(): + app.config["pathod"].clear_log() + return "OK" + + def render(s, cacheable, **kwargs): + kwargs["noapi"] = app.config["pathod"].noapi + kwargs["nocraft"] = app.config["pathod"].nocraft + kwargs["craftanchor"] = app.config["pathod"].craftanchor + resp = make_response(render_template(s, **kwargs), 200) + if cacheable: + resp.headers["Cache-control"] = "public, max-age=4320" + return resp + + @app.route('/') + @app.route('/index.html') + def index(): + return render( + "index.html", + True, + section="main", + version=version.VERSION + ) + + @app.route('/download') + @app.route('/download.html') + def download(): + return render( + "download.html", True, section="download", version=version.VERSION + ) + + @app.route('/about') + @app.route('/about.html') + def about(): + return render("about.html", True, section="about") + + @app.route('/docs/pathod') + def docs_pathod(): + return render( + "docs_pathod.html", True, section="docs", subsection="pathod" + ) + + @app.route('/docs/language') + def docs_language(): + return render( + "docs_lang.html", True, + section="docs", uastrings=user_agents.UASTRINGS, + subsection="lang" + ) + + @app.route('/docs/pathoc') + def docs_pathoc(): + return render( + "docs_pathoc.html", True, section="docs", subsection="pathoc" + ) + + @app.route('/docs/libpathod') + def docs_libpathod(): + return render( + "docs_libpathod.html", True, section="docs", subsection="libpathod" + ) + + @app.route('/docs/test') + def docs_test(): + return render( + "docs_test.html", True, section="docs", subsection="test" + ) + + @app.route('/log') + def log(): + if app.config["pathod"].noapi: + abort(404) + return render( + "log.html", + False, + section="log", + log=app.config["pathod"].get_log() + ) + + @app.route('/log/<int:lid>') + def onelog(lid): + item = app.config["pathod"].log_by_id(int(lid)) + if not item: + abort(404) + l = pprint.pformat(item) + return render("onelog.html", False, section="log", alog=l, lid=lid) + + def _preview(is_request): + if is_request: + template = "request_preview.html" + else: + template = "response_preview.html" + + spec = request.args["spec"] + + args = dict( + spec=spec, + section="main", + syntaxerror=None, + error=None, + ) + if not spec.strip(): + args["error"] = "Can't parse an empty spec." + return render(template, False, **args) + + try: + if is_request: + r = language.parse_pathoc(spec).next() + else: + r = language.parse_pathod(spec).next() + except language.ParseException as v: + args["syntaxerror"] = str(v) + args["marked"] = v.marked() + return render(template, False, **args) + + s = cStringIO.StringIO() + + settings = copy.copy(app.config["pathod"].settings) + settings.request_host = EXAMPLE_HOST + settings.websocket_key = EXAMPLE_WEBSOCKET_KEY + + safe = r.preview_safe() + err, safe = app.config["pathod"].check_policy( + safe, + settings + ) + if err: + args["error"] = err + return render(template, False, **args) + if is_request: + settings.request_host = EXAMPLE_HOST + language.serve(safe, s, settings) + else: + settings.websocket_key = EXAMPLE_WEBSOCKET_KEY + language.serve(safe, s, settings) + + args["output"] = utils.escape_unprintables(s.getvalue()) + return render(template, False, **args) + + @app.route('/response_preview') + def response_preview(): + return _preview(False) + + @app.route('/request_preview') + def request_preview(): + return _preview(True) + return app diff --git a/pathod/libpathod/language/__init__.py b/pathod/libpathod/language/__init__.py new file mode 100644 index 00000000..32199e08 --- /dev/null +++ b/pathod/libpathod/language/__init__.py @@ -0,0 +1,113 @@ +import itertools +import time + +import pyparsing as pp + +from . import http, http2, websockets, writer, exceptions + +from exceptions import * +from base import Settings +assert Settings # prevent pyflakes from messing with this + + +def expand(msg): + times = getattr(msg, "times", None) + if times: + for j_ in xrange(int(times.value)): + yield msg.strike_token("times") + else: + yield msg + + +def parse_pathod(s, use_http2=False): + """ + May raise ParseException + """ + try: + s = s.decode("ascii") + except UnicodeError: + raise exceptions.ParseException("Spec must be valid ASCII.", 0, 0) + try: + if use_http2: + expressions = [ + # http2.Frame.expr(), + http2.Response.expr(), + ] + else: + expressions = [ + websockets.WebsocketFrame.expr(), + http.Response.expr(), + ] + reqs = pp.Or(expressions).parseString(s, parseAll=True) + except pp.ParseException as v: + raise exceptions.ParseException(v.msg, v.line, v.col) + return itertools.chain(*[expand(i) for i in reqs]) + + +def parse_pathoc(s, use_http2=False): + try: + s = s.decode("ascii") + except UnicodeError: + raise exceptions.ParseException("Spec must be valid ASCII.", 0, 0) + try: + if use_http2: + expressions = [ + # http2.Frame.expr(), + http2.Request.expr(), + ] + else: + expressions = [ + websockets.WebsocketClientFrame.expr(), + http.Request.expr(), + ] + reqs = pp.OneOrMore(pp.Or(expressions)).parseString(s, parseAll=True) + except pp.ParseException as v: + raise exceptions.ParseException(v.msg, v.line, v.col) + return itertools.chain(*[expand(i) for i in reqs]) + + +def parse_websocket_frame(s): + """ + May raise ParseException + """ + try: + reqs = pp.OneOrMore( + websockets.WebsocketFrame.expr() + ).parseString( + s, + parseAll=True + ) + except pp.ParseException as v: + raise exceptions.ParseException(v.msg, v.line, v.col) + return itertools.chain(*[expand(i) for i in reqs]) + + +def serve(msg, fp, settings): + """ + fp: The file pointer to write to. + + request_host: If this a request, this is the connecting host. If + None, we assume it's a response. Used to decide what standard + modifications to make if raw is not set. + + Calling this function may modify the object. + """ + msg = msg.resolve(settings) + started = time.time() + + vals = msg.values(settings) + vals.reverse() + + actions = sorted(msg.actions[:]) + actions.reverse() + actions = [i.intermediate(settings) for i in actions] + + disconnect = writer.write_values(fp, vals, actions[:]) + duration = time.time() - started + ret = dict( + disconnect=disconnect, + started=started, + duration=duration, + ) + ret.update(msg.log(settings)) + return ret diff --git a/pathod/libpathod/language/actions.py b/pathod/libpathod/language/actions.py new file mode 100644 index 00000000..34a9bafb --- /dev/null +++ b/pathod/libpathod/language/actions.py @@ -0,0 +1,126 @@ +import abc +import copy +import random + +import pyparsing as pp + +from . import base + + +class _Action(base.Token): + + """ + An action that operates on the raw data stream of the message. All + actions have one thing in common: an offset that specifies where the + action should take place. + """ + + def __init__(self, offset): + self.offset = offset + + def resolve(self, settings, msg): + """ + Resolves offset specifications to a numeric offset. Returns a copy + of the action object. + """ + c = copy.copy(self) + l = msg.length(settings) + if c.offset == "r": + c.offset = random.randrange(l) + elif c.offset == "a": + c.offset = l + 1 + return c + + def __cmp__(self, other): + return cmp(self.offset, other.offset) + + def __repr__(self): + return self.spec() + + @abc.abstractmethod + def spec(self): # pragma: no cover + pass + + @abc.abstractmethod + def intermediate(self, settings): # pragma: no cover + pass + + +class PauseAt(_Action): + unique_name = None + + def __init__(self, offset, seconds): + _Action.__init__(self, offset) + self.seconds = seconds + + @classmethod + def expr(cls): + e = pp.Literal("p").suppress() + e += base.TokOffset + e += pp.Literal(",").suppress() + e += pp.MatchFirst( + [ + base.v_integer, + pp.Literal("f") + ] + ) + return e.setParseAction(lambda x: cls(*x)) + + def spec(self): + return "p%s,%s" % (self.offset, self.seconds) + + def intermediate(self, settings): + return (self.offset, "pause", self.seconds) + + def freeze(self, settings_): + return self + + +class DisconnectAt(_Action): + + def __init__(self, offset): + _Action.__init__(self, offset) + + @classmethod + def expr(cls): + e = pp.Literal("d").suppress() + e += base.TokOffset + return e.setParseAction(lambda x: cls(*x)) + + def spec(self): + return "d%s" % self.offset + + def intermediate(self, settings): + return (self.offset, "disconnect") + + def freeze(self, settings_): + return self + + +class InjectAt(_Action): + unique_name = None + + def __init__(self, offset, value): + _Action.__init__(self, offset) + self.value = value + + @classmethod + def expr(cls): + e = pp.Literal("i").suppress() + e += base.TokOffset + e += pp.Literal(",").suppress() + e += base.TokValue + return e.setParseAction(lambda x: cls(*x)) + + def spec(self): + return "i%s,%s" % (self.offset, self.value.spec()) + + def intermediate(self, settings): + return ( + self.offset, + "inject", + self.value.get_generator(settings) + ) + + def freeze(self, settings): + return InjectAt(self.offset, self.value.freeze(settings)) diff --git a/pathod/libpathod/language/base.py b/pathod/libpathod/language/base.py new file mode 100644 index 00000000..a4302998 --- /dev/null +++ b/pathod/libpathod/language/base.py @@ -0,0 +1,576 @@ +import operator +import os +import abc +import pyparsing as pp + +from .. import utils +from . import generators, exceptions + +class Settings(object): + + def __init__( + self, + is_client=False, + staticdir=None, + unconstrained_file_access=False, + request_host=None, + websocket_key=None, + protocol=None, + ): + self.is_client = is_client + self.staticdir = staticdir + self.unconstrained_file_access = unconstrained_file_access + self.request_host = request_host + self.websocket_key = websocket_key # TODO: refactor this into the protocol + self.protocol = protocol + + +Sep = pp.Optional(pp.Literal(":")).suppress() + + +v_integer = pp.Word(pp.nums)\ + .setName("integer")\ + .setParseAction(lambda toks: int(toks[0])) + + +v_literal = pp.MatchFirst( + [ + pp.QuotedString( + "\"", + unquoteResults=True, + multiline=True + ), + pp.QuotedString( + "'", + unquoteResults=True, + multiline=True + ), + ] +) + +v_naked_literal = pp.MatchFirst( + [ + v_literal, + pp.Word("".join(i for i in pp.printables if i not in ",:\n@\'\"")) + ] +) + + +class Token(object): + + """ + A token in the specification language. Tokens are immutable. The token + classes have no meaning in and of themselves, and are combined into + Components and Actions to build the language. + """ + __metaclass__ = abc.ABCMeta + + @classmethod + def expr(cls): # pragma: no cover + """ + A parse expression. + """ + return None + + @abc.abstractmethod + def spec(self): # pragma: no cover + """ + A parseable specification for this token. + """ + return None + + @property + def unique_name(self): + """ + Controls uniqueness constraints for tokens. No two tokens with the + same name will be allowed. If no uniquness should be applied, this + should be None. + """ + return self.__class__.__name__.lower() + + def resolve(self, settings_, msg_): + """ + Resolves this token to ready it for transmission. This means that + the calculated offsets of actions are fixed. + + settings: a language.Settings instance + msg: The containing message + """ + return self + + def __repr__(self): + return self.spec() + + +class _TokValueLiteral(Token): + + def __init__(self, val): + self.val = val.decode("string_escape") + + def get_generator(self, settings_): + return self.val + + def freeze(self, settings_): + return self + + +class TokValueLiteral(_TokValueLiteral): + + """ + A literal with Python-style string escaping + """ + @classmethod + def expr(cls): + e = v_literal.copy() + return e.setParseAction(cls.parseAction) + + @classmethod + def parseAction(cls, x): + v = cls(*x) + return v + + def spec(self): + inner = self.val.encode("string_escape") + inner = inner.replace(r"\'", r"\x27") + return "'" + inner + "'" + + +class TokValueNakedLiteral(_TokValueLiteral): + + @classmethod + def expr(cls): + e = v_naked_literal.copy() + return e.setParseAction(lambda x: cls(*x)) + + def spec(self): + return self.val.encode("string_escape") + + +class TokValueGenerate(Token): + + def __init__(self, usize, unit, datatype): + if not unit: + unit = "b" + self.usize, self.unit, self.datatype = usize, unit, datatype + + def bytes(self): + return self.usize * utils.SIZE_UNITS[self.unit] + + def get_generator(self, settings_): + return generators.RandomGenerator(self.datatype, self.bytes()) + + def freeze(self, settings): + g = self.get_generator(settings) + return TokValueLiteral(g[:].encode("string_escape")) + + @classmethod + def expr(cls): + e = pp.Literal("@").suppress() + v_integer + + u = reduce( + operator.or_, + [pp.Literal(i) for i in utils.SIZE_UNITS.keys()] + ).leaveWhitespace() + e = e + pp.Optional(u, default=None) + + s = pp.Literal(",").suppress() + s += reduce( + operator.or_, + [pp.Literal(i) for i in generators.DATATYPES.keys()] + ) + e += pp.Optional(s, default="bytes") + return e.setParseAction(lambda x: cls(*x)) + + def spec(self): + s = "@%s" % self.usize + if self.unit != "b": + s += self.unit + if self.datatype != "bytes": + s += ",%s" % self.datatype + return s + + +class TokValueFile(Token): + + def __init__(self, path): + self.path = str(path) + + @classmethod + def expr(cls): + e = pp.Literal("<").suppress() + e = e + v_naked_literal + return e.setParseAction(lambda x: cls(*x)) + + def freeze(self, settings_): + return self + + def get_generator(self, settings): + if not settings.staticdir: + raise exceptions.FileAccessDenied("File access disabled.") + s = os.path.expanduser(self.path) + s = os.path.normpath( + os.path.abspath(os.path.join(settings.staticdir, s)) + ) + uf = settings.unconstrained_file_access + if not uf and not s.startswith(settings.staticdir): + raise exceptions.FileAccessDenied( + "File access outside of configured directory" + ) + if not os.path.isfile(s): + raise exceptions.FileAccessDenied("File not readable") + return generators.FileGenerator(s) + + def spec(self): + return "<'%s'" % self.path.encode("string_escape") + + +TokValue = pp.MatchFirst( + [ + TokValueGenerate.expr(), + TokValueFile.expr(), + TokValueLiteral.expr() + ] +) + + +TokNakedValue = pp.MatchFirst( + [ + TokValueGenerate.expr(), + TokValueFile.expr(), + TokValueLiteral.expr(), + TokValueNakedLiteral.expr(), + ] +) + + +TokOffset = pp.MatchFirst( + [ + v_integer, + pp.Literal("r"), + pp.Literal("a") + ] +) + + +class _Component(Token): + + """ + A value component of the primary specification of an message. + Components produce byte values desribe the bytes of the message. + """ + + def values(self, settings): # pragma: no cover + """ + A sequence of values, which can either be strings or generators. + """ + pass + + def string(self, settings=None): + """ + A string representation of the object. + """ + return "".join(i[:] for i in self.values(settings or {})) + + +class KeyValue(_Component): + + """ + A key/value pair. + cls.preamble: leader + """ + + def __init__(self, key, value): + self.key, self.value = key, value + + @classmethod + def expr(cls): + e = pp.Literal(cls.preamble).suppress() + e += TokValue + e += pp.Literal("=").suppress() + e += TokValue + return e.setParseAction(lambda x: cls(*x)) + + def spec(self): + return "%s%s=%s" % (self.preamble, self.key.spec(), self.value.spec()) + + def freeze(self, settings): + return self.__class__( + self.key.freeze(settings), self.value.freeze(settings) + ) + + +class CaselessLiteral(_Component): + + """ + A caseless token that can take only one value. + """ + + def __init__(self, value): + self.value = value + + @classmethod + def expr(cls): + spec = pp.CaselessLiteral(cls.TOK) + spec = spec.setParseAction(lambda x: cls(*x)) + return spec + + def values(self, settings): + return self.TOK + + def spec(self): + return self.TOK + + def freeze(self, settings_): + return self + + +class OptionsOrValue(_Component): + + """ + Can be any of a specified set of options, or a value specifier. + """ + preamble = "" + options = [] + + def __init__(self, value): + # If it's a string, we were passed one of the options, so we lower-case + # it to be canonical. The user can specify a different case by using a + # string value literal. + self.option_used = False + if isinstance(value, basestring): + for i in self.options: + # Find the exact option value in a case-insensitive way + if i.lower() == value.lower(): + self.option_used = True + value = TokValueLiteral(i) + break + self.value = value + + @classmethod + def expr(cls): + parts = [pp.CaselessLiteral(i) for i in cls.options] + m = pp.MatchFirst(parts) + spec = m | TokValue.copy() + spec = spec.setParseAction(lambda x: cls(*x)) + if cls.preamble: + spec = pp.Literal(cls.preamble).suppress() + spec + return spec + + def values(self, settings): + return [ + self.value.get_generator(settings) + ] + + def spec(self): + s = self.value.spec() + if s[1:-1].lower() in self.options: + s = s[1:-1].lower() + return "%s%s" % (self.preamble, s) + + def freeze(self, settings): + return self.__class__(self.value.freeze(settings)) + + +class Integer(_Component): + bounds = (None, None) + preamble = "" + + def __init__(self, value): + v = int(value) + outofbounds = any([ + self.bounds[0] is not None and v < self.bounds[0], + self.bounds[1] is not None and v > self.bounds[1] + ]) + if outofbounds: + raise exceptions.ParseException( + "Integer value must be between %s and %s." % self.bounds, + 0, 0 + ) + self.value = str(value) + + @classmethod + def expr(cls): + e = v_integer.copy() + if cls.preamble: + e = pp.Literal(cls.preamble).suppress() + e + return e.setParseAction(lambda x: cls(*x)) + + def values(self, settings): + return self.value + + def spec(self): + return "%s%s" % (self.preamble, self.value) + + def freeze(self, settings_): + return self + + +class Value(_Component): + + """ + A value component lead by an optional preamble. + """ + preamble = "" + + def __init__(self, value): + self.value = value + + @classmethod + def expr(cls): + e = (TokValue | TokNakedValue) + if cls.preamble: + e = pp.Literal(cls.preamble).suppress() + e + return e.setParseAction(lambda x: cls(*x)) + + def values(self, settings): + return [self.value.get_generator(settings)] + + def spec(self): + return "%s%s" % (self.preamble, self.value.spec()) + + def freeze(self, settings): + return self.__class__(self.value.freeze(settings)) + + +class FixedLengthValue(Value): + + """ + A value component lead by an optional preamble. + """ + preamble = "" + length = None + + def __init__(self, value): + Value.__init__(self, value) + lenguess = None + try: + lenguess = len(value.get_generator(Settings())) + except exceptions.RenderError: + pass + # This check will fail if we know the length upfront + if lenguess is not None and lenguess != self.length: + raise exceptions.RenderError( + "Invalid value length: '%s' is %s bytes, should be %s." % ( + self.spec(), + lenguess, + self.length + ) + ) + + def values(self, settings): + ret = Value.values(self, settings) + l = sum(len(i) for i in ret) + # This check will fail if we don't know the length upfront - i.e. for + # file inputs + if l != self.length: + raise exceptions.RenderError( + "Invalid value length: '%s' is %s bytes, should be %s." % ( + self.spec(), + l, + self.length + ) + ) + return ret + + +class Boolean(_Component): + + """ + A boolean flag. + name = true + -name = false + """ + name = "" + + def __init__(self, value): + self.value = value + + @classmethod + def expr(cls): + e = pp.Optional(pp.Literal("-"), default=True) + e += pp.Literal(cls.name).suppress() + + def parse(s_, loc_, toks): + val = True + if toks[0] == "-": + val = False + return cls(val) + + return e.setParseAction(parse) + + def spec(self): + return "%s%s" % ("-" if not self.value else "", self.name) + + +class IntField(_Component): + + """ + An integer field, where values can optionally specified by name. + """ + names = {} + max = 16 + preamble = "" + + def __init__(self, value): + self.origvalue = value + self.value = self.names.get(value, value) + if self.value > self.max: + raise exceptions.ParseException( + "Value can't exceed %s" % self.max, 0, 0 + ) + + @classmethod + def expr(cls): + parts = [pp.CaselessLiteral(i) for i in cls.names.keys()] + m = pp.MatchFirst(parts) + spec = m | v_integer.copy() + spec = spec.setParseAction(lambda x: cls(*x)) + if cls.preamble: + spec = pp.Literal(cls.preamble).suppress() + spec + return spec + + def values(self, settings): + return [str(self.value)] + + def spec(self): + return "%s%s" % (self.preamble, self.origvalue) + + +class NestedMessage(Token): + + """ + A nested message, as an escaped string with a preamble. + """ + preamble = "" + nest_type = None + + def __init__(self, value): + Token.__init__(self) + self.value = value + try: + self.parsed = self.nest_type( + self.nest_type.expr().parseString( + value.val, + parseAll=True + ) + ) + except pp.ParseException as v: + raise exceptions.ParseException(v.msg, v.line, v.col) + + @classmethod + def expr(cls): + e = pp.Literal(cls.preamble).suppress() + e = e + TokValueLiteral.expr() + return e.setParseAction(lambda x: cls(*x)) + + def values(self, settings): + return [ + self.value.get_generator(settings), + ] + + def spec(self): + return "%s%s" % (self.preamble, self.value.spec()) + + def freeze(self, settings): + f = self.parsed.freeze(settings).spec() + return self.__class__(TokValueLiteral(f.encode("string_escape"))) diff --git a/pathod/libpathod/language/exceptions.py b/pathod/libpathod/language/exceptions.py new file mode 100644 index 00000000..84ad3c02 --- /dev/null +++ b/pathod/libpathod/language/exceptions.py @@ -0,0 +1,22 @@ + +class RenderError(Exception): + pass + + +class FileAccessDenied(RenderError): + pass + + +class ParseException(Exception): + + def __init__(self, msg, s, col): + Exception.__init__(self) + self.msg = msg + self.s = s + self.col = col + + def marked(self): + return "%s\n%s" % (self.s, " " * (self.col - 1) + "^") + + def __str__(self): + return "%s at char %s" % (self.msg, self.col) diff --git a/pathod/libpathod/language/generators.py b/pathod/libpathod/language/generators.py new file mode 100644 index 00000000..a17e7052 --- /dev/null +++ b/pathod/libpathod/language/generators.py @@ -0,0 +1,86 @@ +import string +import random +import mmap + +DATATYPES = dict( + ascii_letters=string.ascii_letters, + ascii_lowercase=string.ascii_lowercase, + ascii_uppercase=string.ascii_uppercase, + digits=string.digits, + hexdigits=string.hexdigits, + octdigits=string.octdigits, + punctuation=string.punctuation, + whitespace=string.whitespace, + ascii=string.printable, + bytes="".join(chr(i) for i in range(256)) +) + + +class TransformGenerator(object): + + """ + Perform a byte-by-byte transform another generator - that is, for each + input byte, the transformation must produce one output byte. + + gen: A generator to wrap + transform: A function (offset, data) -> transformed + """ + + def __init__(self, gen, transform): + self.gen = gen + self.transform = transform + + def __len__(self): + return len(self.gen) + + def __getitem__(self, x): + d = self.gen.__getitem__(x) + return self.transform(x, d) + + def __getslice__(self, a, b): + d = self.gen.__getslice__(a, b) + return self.transform(a, d) + + def __repr__(self): + return "'transform(%s)'" % self.gen + + +class RandomGenerator(object): + + def __init__(self, dtype, length): + self.dtype = dtype + self.length = length + + def __len__(self): + return self.length + + def __getitem__(self, x): + return random.choice(DATATYPES[self.dtype]) + + def __getslice__(self, a, b): + b = min(b, self.length) + chars = DATATYPES[self.dtype] + return "".join(random.choice(chars) for x in range(a, b)) + + def __repr__(self): + return "%s random from %s" % (self.length, self.dtype) + + +class FileGenerator(object): + + def __init__(self, path): + self.path = path + self.fp = file(path, "rb") + self.map = mmap.mmap(self.fp.fileno(), 0, access=mmap.ACCESS_READ) + + def __len__(self): + return len(self.map) + + def __getitem__(self, x): + return self.map.__getitem__(x) + + def __getslice__(self, a, b): + return self.map.__getslice__(a, b) + + def __repr__(self): + return "<%s" % self.path diff --git a/pathod/libpathod/language/http.py b/pathod/libpathod/language/http.py new file mode 100644 index 00000000..a82f12fe --- /dev/null +++ b/pathod/libpathod/language/http.py @@ -0,0 +1,381 @@ + +import abc + +import pyparsing as pp + +import netlib.websockets +from netlib.http import status_codes, user_agents +from . import base, exceptions, actions, message + +# TODO: use netlib.semantics.protocol assemble method, +# instead of duplicating the HTTP on-the-wire representation here. +# see http2 language for an example + +class WS(base.CaselessLiteral): + TOK = "ws" + + +class Raw(base.CaselessLiteral): + TOK = "r" + + +class Path(base.Value): + pass + + +class StatusCode(base.Integer): + pass + + +class Reason(base.Value): + preamble = "m" + + +class Body(base.Value): + preamble = "b" + + +class Times(base.Integer): + preamble = "x" + + +class Method(base.OptionsOrValue): + options = [ + "GET", + "HEAD", + "POST", + "PUT", + "DELETE", + "OPTIONS", + "TRACE", + "CONNECT", + ] + + +class _HeaderMixin(object): + unique_name = None + + def format_header(self, key, value): + return [key, ": ", value, "\r\n"] + + def values(self, settings): + return self.format_header( + self.key.get_generator(settings), + self.value.get_generator(settings), + ) + + +class Header(_HeaderMixin, base.KeyValue): + preamble = "h" + + +class ShortcutContentType(_HeaderMixin, base.Value): + preamble = "c" + key = base.TokValueLiteral("Content-Type") + + +class ShortcutLocation(_HeaderMixin, base.Value): + preamble = "l" + key = base.TokValueLiteral("Location") + + +class ShortcutUserAgent(_HeaderMixin, base.OptionsOrValue): + preamble = "u" + options = [i[1] for i in user_agents.UASTRINGS] + key = base.TokValueLiteral("User-Agent") + + def values(self, settings): + value = self.value.val + if self.option_used: + value = user_agents.get_by_shortcut(value.lower())[2] + + return self.format_header( + self.key.get_generator(settings), + value + ) + + +def get_header(val, headers): + """ + Header keys may be Values, so we have to "generate" them as we try the + match. + """ + for h in headers: + k = h.key.get_generator({}) + if len(k) == len(val) and k[:].lower() == val.lower(): + return h + return None + + +class _HTTPMessage(message.Message): + version = "HTTP/1.1" + + @property + def actions(self): + return self.toks(actions._Action) + + @property + def raw(self): + return bool(self.tok(Raw)) + + @property + def body(self): + return self.tok(Body) + + @abc.abstractmethod + def preamble(self, settings): # pragma: no cover + pass + + @property + def headers(self): + return self.toks(_HeaderMixin) + + def values(self, settings): + vals = self.preamble(settings) + vals.append("\r\n") + for h in self.headers: + vals.extend(h.values(settings)) + vals.append("\r\n") + if self.body: + vals.extend(self.body.values(settings)) + return vals + + +class Response(_HTTPMessage): + unique_name = None + comps = ( + Header, + ShortcutContentType, + ShortcutLocation, + Raw, + Reason, + Body, + + actions.PauseAt, + actions.DisconnectAt, + actions.InjectAt, + ) + logattrs = ["status_code", "reason", "version", "body"] + + @property + def ws(self): + return self.tok(WS) + + @property + def status_code(self): + return self.tok(StatusCode) + + @property + def reason(self): + return self.tok(Reason) + + def preamble(self, settings): + l = [self.version, " "] + l.extend(self.status_code.values(settings)) + status_code = int(self.status_code.value) + l.append(" ") + if self.reason: + l.extend(self.reason.values(settings)) + else: + l.append( + status_codes.RESPONSES.get( + status_code, + "Unknown code" + ) + ) + return l + + def resolve(self, settings, msg=None): + tokens = self.tokens[:] + if self.ws: + if not settings.websocket_key: + raise exceptions.RenderError( + "No websocket key - have we seen a client handshake?" + ) + if not self.status_code: + tokens.insert( + 1, + StatusCode(101) + ) + headers = netlib.websockets.WebsocketsProtocol.server_handshake_headers( + settings.websocket_key + ) + for i in headers.fields: + if not get_header(i[0], self.headers): + tokens.append( + Header( + base.TokValueLiteral(i[0]), + base.TokValueLiteral(i[1])) + ) + if not self.raw: + if not get_header("Content-Length", self.headers): + if not self.body: + length = 0 + else: + length = sum( + len(i) for i in self.body.values(settings) + ) + tokens.append( + Header( + base.TokValueLiteral("Content-Length"), + base.TokValueLiteral(str(length)), + ) + ) + intermediate = self.__class__(tokens) + return self.__class__( + [i.resolve(settings, intermediate) for i in tokens] + ) + + @classmethod + def expr(cls): + parts = [i.expr() for i in cls.comps] + atom = pp.MatchFirst(parts) + resp = pp.And( + [ + pp.MatchFirst( + [ + WS.expr() + pp.Optional( + base.Sep + StatusCode.expr() + ), + StatusCode.expr(), + ] + ), + pp.ZeroOrMore(base.Sep + atom) + ] + ) + resp = resp.setParseAction(cls) + return resp + + def spec(self): + return ":".join([i.spec() for i in self.tokens]) + + +class NestedResponse(base.NestedMessage): + preamble = "s" + nest_type = Response + + +class Request(_HTTPMessage): + comps = ( + Header, + ShortcutContentType, + ShortcutUserAgent, + Raw, + NestedResponse, + Body, + Times, + + actions.PauseAt, + actions.DisconnectAt, + actions.InjectAt, + ) + logattrs = ["method", "path", "body"] + + @property + def ws(self): + return self.tok(WS) + + @property + def method(self): + return self.tok(Method) + + @property + def path(self): + return self.tok(Path) + + @property + def times(self): + return self.tok(Times) + + @property + def nested_response(self): + return self.tok(NestedResponse) + + def preamble(self, settings): + v = self.method.values(settings) + v.append(" ") + v.extend(self.path.values(settings)) + if self.nested_response: + v.append(self.nested_response.parsed.spec()) + v.append(" ") + v.append(self.version) + return v + + def resolve(self, settings, msg=None): + tokens = self.tokens[:] + if self.ws: + if not self.method: + tokens.insert( + 1, + Method("get") + ) + for i in netlib.websockets.WebsocketsProtocol.client_handshake_headers().fields: + if not get_header(i[0], self.headers): + tokens.append( + Header( + base.TokValueLiteral(i[0]), + base.TokValueLiteral(i[1]) + ) + ) + if not self.raw: + if not get_header("Content-Length", self.headers): + if self.body: + length = sum( + len(i) for i in self.body.values(settings) + ) + tokens.append( + Header( + base.TokValueLiteral("Content-Length"), + base.TokValueLiteral(str(length)), + ) + ) + if settings.request_host: + if not get_header("Host", self.headers): + tokens.append( + Header( + base.TokValueLiteral("Host"), + base.TokValueLiteral(settings.request_host) + ) + ) + intermediate = self.__class__(tokens) + return self.__class__( + [i.resolve(settings, intermediate) for i in tokens] + ) + + @classmethod + def expr(cls): + parts = [i.expr() for i in cls.comps] + atom = pp.MatchFirst(parts) + resp = pp.And( + [ + pp.MatchFirst( + [ + WS.expr() + pp.Optional( + base.Sep + Method.expr() + ), + Method.expr(), + ] + ), + base.Sep, + Path.expr(), + pp.ZeroOrMore(base.Sep + atom) + ] + ) + resp = resp.setParseAction(cls) + return resp + + def spec(self): + return ":".join([i.spec() for i in self.tokens]) + + +def make_error_response(reason, body=None): + tokens = [ + StatusCode("800"), + Header( + base.TokValueLiteral("Content-Type"), + base.TokValueLiteral("text/plain") + ), + Reason(base.TokValueLiteral(reason)), + Body(base.TokValueLiteral("pathod error: " + (body or reason))), + ] + return Response(tokens) diff --git a/pathod/libpathod/language/http2.py b/pathod/libpathod/language/http2.py new file mode 100644 index 00000000..d5e3ca31 --- /dev/null +++ b/pathod/libpathod/language/http2.py @@ -0,0 +1,299 @@ +import pyparsing as pp + +from netlib import http +from netlib.http import user_agents, Headers +from . import base, message + +""" + Normal HTTP requests: + <method>:<path>:<header>:<body> + e.g.: + GET:/ + GET:/:h"foo"="bar" + POST:/:h"foo"="bar":b'content body payload' + + Normal HTTP responses: + <code>:<header>:<body> + e.g.: + 200 + 302:h"foo"="bar" + 404:h"foo"="bar":b'content body payload' + + Individual HTTP/2 frames: + h2f:<payload_length>:<type>:<flags>:<stream_id>:<payload> + e.g.: + h2f:0:PING + h2f:42:HEADERS:END_HEADERS:0x1234567:foo=bar,host=example.com + h2f:42:DATA:END_STREAM,PADDED:0x1234567:'content body payload' +""" + +def get_header(val, headers): + """ + Header keys may be Values, so we have to "generate" them as we try the + match. + """ + for h in headers: + k = h.key.get_generator({}) + if len(k) == len(val) and k[:].lower() == val.lower(): + return h + return None + + +class _HeaderMixin(object): + unique_name = None + + def values(self, settings): + return ( + self.key.get_generator(settings), + self.value.get_generator(settings), + ) + +class _HTTP2Message(message.Message): + @property + def actions(self): + return [] # self.toks(actions._Action) + + @property + def headers(self): + headers = self.toks(_HeaderMixin) + + if not self.raw: + if not get_header("content-length", headers): + if not self.body: + length = 0 + else: + length = len(self.body.string()) + headers.append( + Header( + base.TokValueLiteral("content-length"), + base.TokValueLiteral(str(length)), + ) + ) + return headers + + @property + def raw(self): + return bool(self.tok(Raw)) + + @property + def body(self): + return self.tok(Body) + + def resolve(self, settings): + return self + + +class StatusCode(base.Integer): + pass + + +class Method(base.OptionsOrValue): + options = [ + "GET", + "HEAD", + "POST", + "PUT", + "DELETE", + ] + + +class Path(base.Value): + pass + + +class Header(_HeaderMixin, base.KeyValue): + preamble = "h" + + +class ShortcutContentType(_HeaderMixin, base.Value): + preamble = "c" + key = base.TokValueLiteral("content-type") + + +class ShortcutLocation(_HeaderMixin, base.Value): + preamble = "l" + key = base.TokValueLiteral("location") + + +class ShortcutUserAgent(_HeaderMixin, base.OptionsOrValue): + preamble = "u" + options = [i[1] for i in user_agents.UASTRINGS] + key = base.TokValueLiteral("user-agent") + + def values(self, settings): + value = self.value.val + if self.option_used: + value = user_agents.get_by_shortcut(value.lower())[2] + + return ( + self.key.get_generator(settings), + value + ) + + +class Raw(base.CaselessLiteral): + TOK = "r" + + +class Body(base.Value): + preamble = "b" + + +class Times(base.Integer): + preamble = "x" + + +class Response(_HTTP2Message): + unique_name = None + comps = ( + Header, + Body, + ShortcutContentType, + ShortcutLocation, + Raw, + ) + + def __init__(self, tokens): + super(Response, self).__init__(tokens) + self.rendered_values = None + self.stream_id = 2 + + @property + def status_code(self): + return self.tok(StatusCode) + + @classmethod + def expr(cls): + parts = [i.expr() for i in cls.comps] + atom = pp.MatchFirst(parts) + resp = pp.And( + [ + StatusCode.expr(), + pp.ZeroOrMore(base.Sep + atom) + ] + ) + resp = resp.setParseAction(cls) + return resp + + def values(self, settings): + if self.rendered_values: + return self.rendered_values + else: + headers = Headers([header.values(settings) for header in self.headers]) + + body = self.body + if body: + body = body.string() + + resp = http.Response( + (2, 0), + self.status_code.string(), + '', + headers, + body, + ) + resp.stream_id = self.stream_id + + self.rendered_values = settings.protocol.assemble(resp) + return self.rendered_values + + def spec(self): + return ":".join([i.spec() for i in self.tokens]) + + +class NestedResponse(base.NestedMessage): + preamble = "s" + nest_type = Response + + +class Request(_HTTP2Message): + comps = ( + Header, + ShortcutContentType, + ShortcutUserAgent, + Raw, + NestedResponse, + Body, + Times, + ) + logattrs = ["method", "path"] + + def __init__(self, tokens): + super(Request, self).__init__(tokens) + self.rendered_values = None + self.stream_id = 1 + + @property + def method(self): + return self.tok(Method) + + @property + def path(self): + return self.tok(Path) + + @property + def nested_response(self): + return self.tok(NestedResponse) + + @property + def times(self): + return self.tok(Times) + + @classmethod + def expr(cls): + parts = [i.expr() for i in cls.comps] + atom = pp.MatchFirst(parts) + resp = pp.And( + [ + Method.expr(), + base.Sep, + Path.expr(), + pp.ZeroOrMore(base.Sep + atom) + ] + ) + resp = resp.setParseAction(cls) + return resp + + def values(self, settings): + if self.rendered_values: + return self.rendered_values + else: + path = self.path.string() + if self.nested_response: + path += self.nested_response.parsed.spec() + + headers = Headers([header.values(settings) for header in self.headers]) + + body = self.body + if body: + body = body.string() + + req = http.Request( + '', + self.method.string(), + '', + '', + '', + path, + (2, 0), + headers, + body, + ) + req.stream_id = self.stream_id + + self.rendered_values = settings.protocol.assemble(req) + return self.rendered_values + + def spec(self): + return ":".join([i.spec() for i in self.tokens]) + +def make_error_response(reason, body=None): + tokens = [ + StatusCode("800"), + Body(base.TokValueLiteral("pathod error: " + (body or reason))), + ] + return Response(tokens) + + +# class Frame(message.Message): +# pass diff --git a/pathod/libpathod/language/message.py b/pathod/libpathod/language/message.py new file mode 100644 index 00000000..33124856 --- /dev/null +++ b/pathod/libpathod/language/message.py @@ -0,0 +1,96 @@ +import abc +from . import actions, exceptions + +LOG_TRUNCATE = 1024 + + +class Message(object): + __metaclass__ = abc.ABCMeta + logattrs = [] + + def __init__(self, tokens): + track = set([]) + for i in tokens: + if i.unique_name: + if i.unique_name in track: + raise exceptions.ParseException( + "Message has multiple %s clauses, " + "but should only have one." % i.unique_name, + 0, 0 + ) + else: + track.add(i.unique_name) + self.tokens = tokens + + def strike_token(self, name): + toks = [i for i in self.tokens if i.unique_name != name] + return self.__class__(toks) + + def toks(self, klass): + """ + Fetch all tokens that are instances of klass + """ + return [i for i in self.tokens if isinstance(i, klass)] + + def tok(self, klass): + """ + Fetch first token that is an instance of klass + """ + l = self.toks(klass) + if l: + return l[0] + + def length(self, settings): + """ + Calculate the length of the base message without any applied + actions. + """ + return sum(len(x) for x in self.values(settings)) + + def preview_safe(self): + """ + Return a copy of this message that issafe for previews. + """ + tokens = [i for i in self.tokens if not isinstance(i, actions.PauseAt)] + return self.__class__(tokens) + + def maximum_length(self, settings): + """ + Calculate the maximum length of the base message with all applied + actions. + """ + l = self.length(settings) + for i in self.actions: + if isinstance(i, actions.InjectAt): + l += len(i.value.get_generator(settings)) + return l + + @classmethod + def expr(cls): # pragma: no cover + pass + + def log(self, settings): + """ + A dictionary that should be logged if this message is served. + """ + ret = {} + for i in self.logattrs: + v = getattr(self, i) + # Careful not to log any VALUE specs without sanitizing them first. + # We truncate at 1k. + if hasattr(v, "values"): + v = [x[:LOG_TRUNCATE] for x in v.values(settings)] + v = "".join(v).encode("string_escape") + elif hasattr(v, "__len__"): + v = v[:LOG_TRUNCATE] + v = v.encode("string_escape") + ret[i] = v + ret["spec"] = self.spec() + return ret + + def freeze(self, settings): + r = self.resolve(settings) + return self.__class__([i.freeze(settings) for i in r.tokens]) + + def __repr__(self): + return self.spec() diff --git a/pathod/libpathod/language/websockets.py b/pathod/libpathod/language/websockets.py new file mode 100644 index 00000000..ea7c870e --- /dev/null +++ b/pathod/libpathod/language/websockets.py @@ -0,0 +1,241 @@ +import os +import netlib.websockets +import pyparsing as pp +from . import base, generators, actions, message + +NESTED_LEADER = "pathod!" + + +class WF(base.CaselessLiteral): + TOK = "wf" + + +class OpCode(base.IntField): + names = { + "continue": netlib.websockets.OPCODE.CONTINUE, + "text": netlib.websockets.OPCODE.TEXT, + "binary": netlib.websockets.OPCODE.BINARY, + "close": netlib.websockets.OPCODE.CLOSE, + "ping": netlib.websockets.OPCODE.PING, + "pong": netlib.websockets.OPCODE.PONG, + } + max = 15 + preamble = "c" + + +class Body(base.Value): + preamble = "b" + + +class RawBody(base.Value): + unique_name = "body" + preamble = "r" + + +class Fin(base.Boolean): + name = "fin" + + +class RSV1(base.Boolean): + name = "rsv1" + + +class RSV2(base.Boolean): + name = "rsv2" + + +class RSV3(base.Boolean): + name = "rsv3" + + +class Mask(base.Boolean): + name = "mask" + + +class Key(base.FixedLengthValue): + preamble = "k" + length = 4 + + +class KeyNone(base.CaselessLiteral): + unique_name = "key" + TOK = "knone" + + +class Length(base.Integer): + bounds = (0, 1 << 64) + preamble = "l" + + +class Times(base.Integer): + preamble = "x" + + +COMPONENTS = ( + OpCode, + Length, + # Bit flags + Fin, + RSV1, + RSV2, + RSV3, + Mask, + actions.PauseAt, + actions.DisconnectAt, + actions.InjectAt, + KeyNone, + Key, + Times, + + Body, + RawBody, +) + + +class WebsocketFrame(message.Message): + components = COMPONENTS + logattrs = ["body"] + # Used for nested frames + unique_name = "body" + + @property + def actions(self): + return self.toks(actions._Action) + + @property + def body(self): + return self.tok(Body) + + @property + def rawbody(self): + return self.tok(RawBody) + + @property + def opcode(self): + return self.tok(OpCode) + + @property + def fin(self): + return self.tok(Fin) + + @property + def rsv1(self): + return self.tok(RSV1) + + @property + def rsv2(self): + return self.tok(RSV2) + + @property + def rsv3(self): + return self.tok(RSV3) + + @property + def mask(self): + return self.tok(Mask) + + @property + def key(self): + return self.tok(Key) + + @property + def knone(self): + return self.tok(KeyNone) + + @property + def times(self): + return self.tok(Times) + + @property + def toklength(self): + return self.tok(Length) + + @classmethod + def expr(cls): + parts = [i.expr() for i in cls.components] + atom = pp.MatchFirst(parts) + resp = pp.And( + [ + WF.expr(), + base.Sep, + pp.ZeroOrMore(base.Sep + atom) + ] + ) + resp = resp.setParseAction(cls) + return resp + + @property + def nested_frame(self): + return self.tok(NestedFrame) + + def resolve(self, settings, msg=None): + tokens = self.tokens[:] + if not self.mask and settings.is_client: + tokens.append( + Mask(True) + ) + if not self.knone and self.mask and self.mask.value and not self.key: + tokens.append( + Key(base.TokValueLiteral(os.urandom(4))) + ) + return self.__class__( + [i.resolve(settings, self) for i in tokens] + ) + + def values(self, settings): + if self.body: + bodygen = self.body.value.get_generator(settings) + length = len(self.body.value.get_generator(settings)) + elif self.rawbody: + bodygen = self.rawbody.value.get_generator(settings) + length = len(self.rawbody.value.get_generator(settings)) + elif self.nested_frame: + bodygen = NESTED_LEADER + self.nested_frame.parsed.spec() + length = len(bodygen) + else: + bodygen = None + length = 0 + if self.toklength: + length = int(self.toklength.value) + frameparts = dict( + payload_length=length + ) + if self.mask and self.mask.value: + frameparts["mask"] = True + if self.knone: + frameparts["masking_key"] = None + elif self.key: + key = self.key.values(settings)[0][:] + frameparts["masking_key"] = key + for i in ["opcode", "fin", "rsv1", "rsv2", "rsv3", "mask"]: + v = getattr(self, i, None) + if v is not None: + frameparts[i] = v.value + frame = netlib.websockets.FrameHeader(**frameparts) + vals = [bytes(frame)] + if bodygen: + if frame.masking_key and not self.rawbody: + masker = netlib.websockets.Masker(frame.masking_key) + vals.append( + generators.TransformGenerator( + bodygen, + masker.mask + ) + ) + else: + vals.append(bodygen) + return vals + + def spec(self): + return ":".join([i.spec() for i in self.tokens]) + + +class NestedFrame(base.NestedMessage): + preamble = "f" + nest_type = WebsocketFrame + + +class WebsocketClientFrame(WebsocketFrame): + components = COMPONENTS + ( + NestedFrame, + ) diff --git a/pathod/libpathod/language/writer.py b/pathod/libpathod/language/writer.py new file mode 100644 index 00000000..1a27e1ef --- /dev/null +++ b/pathod/libpathod/language/writer.py @@ -0,0 +1,67 @@ +import time +from netlib.exceptions import TcpDisconnect +import netlib.tcp + +BLOCKSIZE = 1024 +# It's not clear what the upper limit for time.sleep is. It's lower than the +# maximum int or float. 1 year should do. +FOREVER = 60 * 60 * 24 * 365 + + +def send_chunk(fp, val, blocksize, start, end): + """ + (start, end): Inclusive lower bound, exclusive upper bound. + """ + for i in range(start, end, blocksize): + fp.write( + val[i:min(i + blocksize, end)] + ) + return end - start + + +def write_values(fp, vals, actions, sofar=0, blocksize=BLOCKSIZE): + """ + vals: A list of values, which may be strings or Value objects. + + actions: A list of (offset, action, arg) tuples. Action may be "pause" + or "disconnect". + + Both vals and actions are in reverse order, with the first items last. + + Return True if connection should disconnect. + """ + sofar = 0 + try: + while vals: + v = vals.pop() + offset = 0 + while actions and actions[-1][0] < (sofar + len(v)): + a = actions.pop() + offset += send_chunk( + fp, + v, + blocksize, + offset, + a[0] - sofar - offset + ) + if a[1] == "pause": + time.sleep( + FOREVER if a[2] == "f" else a[2] + ) + elif a[1] == "disconnect": + return True + elif a[1] == "inject": + send_chunk(fp, a[2], blocksize, 0, len(a[2])) + send_chunk(fp, v, blocksize, offset, len(v)) + sofar += len(v) + # Remainders + while actions: + a = actions.pop() + if a[1] == "pause": + time.sleep(a[2]) + elif a[1] == "disconnect": + return True + elif a[1] == "inject": + send_chunk(fp, a[2], blocksize, 0, len(a[2])) + except TcpDisconnect: # pragma: no cover + return True diff --git a/pathod/libpathod/log.py b/pathod/libpathod/log.py new file mode 100644 index 00000000..f203542f --- /dev/null +++ b/pathod/libpathod/log.py @@ -0,0 +1,83 @@ +import datetime + +import netlib.utils +import netlib.tcp +import netlib.http + +TIMEFMT = '%d-%m-%y %H:%M:%S' + + +def write_raw(fp, lines): + if fp: + fp.write( + "%s: " % datetime.datetime.now().strftime(TIMEFMT) + ) + for i in lines: + fp.write(i) + fp.write("\n") + fp.flush() + + +class LogCtx(object): + + def __init__(self, fp, hex, rfile, wfile): + self.lines = [] + self.fp = fp + self.suppressed = False + self.hex = hex + self.rfile, self.wfile = rfile, wfile + + def __enter__(self): + if self.wfile: + self.wfile.start_log() + if self.rfile: + self.rfile.start_log() + return self + + def __exit__(self, exc_type, exc_value, traceback): + wlog = self.wfile.get_log() if self.wfile else None + rlog = self.rfile.get_log() if self.rfile else None + if self.suppressed or not self.fp: + return + if wlog: + self("Bytes written:") + self.dump(wlog, self.hex) + if rlog: + self("Bytes read:") + self.dump(rlog, self.hex) + if self.lines: + write_raw( + self.fp, + [ + "\n".join(self.lines), + ] + ) + if exc_value: + raise exc_type, exc_value, traceback + + def suppress(self): + self.suppressed = True + + def dump(self, data, hexdump): + if hexdump: + for line in netlib.utils.hexdump(data): + self("\t%s %s %s" % line) + else: + for i in netlib.utils.clean_bin(data).split("\n"): + self("\t%s" % i) + + def __call__(self, line): + self.lines.append(line) + + +class ConnectionLogger: + def __init__(self, fp, hex, rfile, wfile): + self.fp = fp + self.hex = hex + self.rfile, self.wfile = rfile, wfile + + def ctx(self): + return LogCtx(self.fp, self.hex, self.rfile, self.wfile) + + def write(self, lines): + write_raw(self.fp, lines) diff --git a/pathod/libpathod/pathoc.py b/pathod/libpathod/pathoc.py new file mode 100644 index 00000000..55c2a6e0 --- /dev/null +++ b/pathod/libpathod/pathoc.py @@ -0,0 +1,534 @@ +import contextlib +import sys +import os +import itertools +import hashlib +import Queue +import random +import select +import time +import threading + +import OpenSSL.crypto +import six + +from netlib import tcp, http, certutils, websockets, socks +from netlib.exceptions import HttpException, TcpDisconnect, TcpTimeout, TlsException, TcpException, \ + NetlibException +from netlib.http import http1, http2 + +import language.http +import language.websockets +from . import utils, log + +import logging +from netlib.tutils import treq + +logging.getLogger("hpack").setLevel(logging.WARNING) + + +class PathocError(Exception): + pass + + +class SSLInfo(object): + + def __init__(self, certchain, cipher, alp): + self.certchain, self.cipher, self.alp = certchain, cipher, alp + + def __str__(self): + parts = [ + "Application Layer Protocol: %s" % self.alp, + "Cipher: %s, %s bit, %s" % self.cipher, + "SSL certificate chain:" + ] + for i in self.certchain: + parts.append("\tSubject: ") + for cn in i.get_subject().get_components(): + parts.append("\t\t%s=%s" % cn) + parts.append("\tIssuer: ") + for cn in i.get_issuer().get_components(): + parts.append("\t\t%s=%s" % cn) + parts.extend( + [ + "\tVersion: %s" % i.get_version(), + "\tValidity: %s - %s" % ( + i.get_notBefore(), i.get_notAfter() + ), + "\tSerial: %s" % i.get_serial_number(), + "\tAlgorithm: %s" % i.get_signature_algorithm() + ] + ) + pk = i.get_pubkey() + types = { + OpenSSL.crypto.TYPE_RSA: "RSA", + OpenSSL.crypto.TYPE_DSA: "DSA" + } + t = types.get(pk.type(), "Uknown") + parts.append("\tPubkey: %s bit %s" % (pk.bits(), t)) + s = certutils.SSLCert(i) + if s.altnames: + parts.append("\tSANs: %s" % " ".join(s.altnames)) + return "\n".join(parts) + + + +class WebsocketFrameReader(threading.Thread): + + def __init__( + self, + rfile, + logfp, + showresp, + hexdump, + ws_read_limit, + timeout + ): + threading.Thread.__init__(self) + self.timeout = timeout + self.ws_read_limit = ws_read_limit + self.logfp = logfp + self.showresp = showresp + self.hexdump = hexdump + self.rfile = rfile + self.terminate = Queue.Queue() + self.frames_queue = Queue.Queue() + self.logger = log.ConnectionLogger( + self.logfp, + self.hexdump, + rfile if showresp else None, + None + ) + + @contextlib.contextmanager + def terminator(self): + yield + self.frames_queue.put(None) + + def run(self): + starttime = time.time() + with self.terminator(): + while True: + if self.ws_read_limit == 0: + return + r, _, _ = select.select([self.rfile], [], [], 0.05) + delta = time.time() - starttime + if not r and self.timeout and delta > self.timeout: + return + try: + self.terminate.get_nowait() + return + except Queue.Empty: + pass + for rfile in r: + with self.logger.ctx() as log: + try: + frm = websockets.Frame.from_file(self.rfile) + except TcpDisconnect: + return + self.frames_queue.put(frm) + log("<< %s" % frm.header.human_readable()) + if self.ws_read_limit is not None: + self.ws_read_limit -= 1 + starttime = time.time() + + +class Pathoc(tcp.TCPClient): + + def __init__( + self, + address, + + # SSL + ssl=None, + sni=None, + ssl_version=tcp.SSL_DEFAULT_METHOD, + ssl_options=tcp.SSL_DEFAULT_OPTIONS, + clientcert=None, + ciphers=None, + + # HTTP/2 + use_http2=False, + http2_skip_connection_preface=False, + http2_framedump=False, + + # Websockets + ws_read_limit=None, + + # Network + timeout=None, + + # Output control + showreq=False, + showresp=False, + explain=False, + hexdump=False, + ignorecodes=(), + ignoretimeout=False, + showsummary=False, + fp=sys.stdout + ): + """ + spec: A request specification + showreq: Print requests + showresp: Print responses + explain: Print request explanation + showssl: Print info on SSL connection + hexdump: When printing requests or responses, use hex dump output + showsummary: Show a summary of requests + ignorecodes: Sequence of return codes to ignore + """ + tcp.TCPClient.__init__(self, address) + + self.ssl, self.sni = ssl, sni + self.clientcert = clientcert + self.ssl_version = ssl_version + self.ssl_options = ssl_options + self.ciphers = ciphers + self.sslinfo = None + + self.use_http2 = use_http2 + self.http2_skip_connection_preface = http2_skip_connection_preface + self.http2_framedump = http2_framedump + + self.ws_read_limit = ws_read_limit + + self.timeout = timeout + + self.showreq = showreq + self.showresp = showresp + self.explain = explain + self.hexdump = hexdump + self.ignorecodes = ignorecodes + self.ignoretimeout = ignoretimeout + self.showsummary = showsummary + self.fp = fp + + self.ws_framereader = None + + if self.use_http2: + if not OpenSSL._util.lib.Cryptography_HAS_ALPN: # pragma: nocover + log.write_raw( + self.fp, + "HTTP/2 requires ALPN support. " + "Please use OpenSSL >= 1.0.2. " + "Pathoc might not be working as expected without ALPN." + ) + self.protocol = http2.HTTP2Protocol(self, dump_frames=self.http2_framedump) + else: + self.protocol = http1 + + self.settings = language.Settings( + is_client=True, + staticdir=os.getcwd(), + unconstrained_file_access=True, + request_host=self.address.host, + protocol=self.protocol, + ) + + def http_connect(self, connect_to): + self.wfile.write( + 'CONNECT %s:%s HTTP/1.1\r\n' % tuple(connect_to) + + '\r\n' + ) + self.wfile.flush() + try: + resp = self.protocol.read_response(self.rfile, treq(method="CONNECT")) + if resp.status_code != 200: + raise HttpException("Unexpected status code: %s" % resp.status_code) + except HttpException as e: + six.reraise(PathocError, PathocError( + "Proxy CONNECT failed: %s" % repr(e) + )) + + def socks_connect(self, connect_to): + try: + client_greet = socks.ClientGreeting(socks.VERSION.SOCKS5, [socks.METHOD.NO_AUTHENTICATION_REQUIRED]) + client_greet.to_file(self.wfile) + self.wfile.flush() + + server_greet = socks.ServerGreeting.from_file(self.rfile) + server_greet.assert_socks5() + if server_greet.method != socks.METHOD.NO_AUTHENTICATION_REQUIRED: + raise socks.SocksError( + socks.METHOD.NO_ACCEPTABLE_METHODS, + "pathoc only supports SOCKS without authentication" + ) + + connect_request = socks.Message( + socks.VERSION.SOCKS5, + socks.CMD.CONNECT, + socks.ATYP.DOMAINNAME, + tcp.Address.wrap(connect_to) + ) + connect_request.to_file(self.wfile) + self.wfile.flush() + + connect_reply = socks.Message.from_file(self.rfile) + connect_reply.assert_socks5() + if connect_reply.msg != socks.REP.SUCCEEDED: + raise socks.SocksError( + connect_reply.msg, + "SOCKS server error" + ) + except (socks.SocksError, TcpDisconnect) as e: + raise PathocError(str(e)) + + def connect(self, connect_to=None, showssl=False, fp=sys.stdout): + """ + connect_to: A (host, port) tuple, which will be connected to with + an HTTP CONNECT request. + """ + if self.use_http2 and not self.ssl: + raise NotImplementedError("HTTP2 without SSL is not supported.") + + tcp.TCPClient.connect(self) + + if connect_to: + self.http_connect(connect_to) + + self.sslinfo = None + if self.ssl: + try: + alpn_protos = [b'http/1.1'] + if self.use_http2: + alpn_protos.append(b'h2') + + self.convert_to_ssl( + sni=self.sni, + cert=self.clientcert, + method=self.ssl_version, + options=self.ssl_options, + cipher_list=self.ciphers, + alpn_protos=alpn_protos + ) + except TlsException as v: + raise PathocError(str(v)) + + self.sslinfo = SSLInfo( + self.connection.get_peer_cert_chain(), + self.get_current_cipher(), + self.get_alpn_proto_negotiated() + ) + if showssl: + print >> fp, str(self.sslinfo) + + if self.use_http2: + self.protocol.check_alpn() + if not self.http2_skip_connection_preface: + self.protocol.perform_client_connection_preface() + + if self.timeout: + self.settimeout(self.timeout) + + def stop(self): + if self.ws_framereader: + self.ws_framereader.terminate.put(None) + + def wait(self, timeout=0.01, finish=True): + """ + A generator that yields frames until Pathoc terminates. + + timeout: If specified None may be yielded instead if timeout is + reached. If timeout is None, wait forever. If timeout is 0, return + immedately if nothing is on the queue. + + finish: If true, consume messages until the reader shuts down. + Otherwise, return None on timeout. + """ + if self.ws_framereader: + while True: + try: + frm = self.ws_framereader.frames_queue.get( + timeout=timeout, + block=True if timeout != 0 else False + ) + except Queue.Empty: + if finish: + continue + else: + return + if frm is None: + self.ws_framereader.join() + return + yield frm + + def websocket_send_frame(self, r): + """ + Sends a single websocket frame. + """ + logger = log.ConnectionLogger( + self.fp, + self.hexdump, + None, + self.wfile if self.showreq else None, + ) + with logger.ctx() as lg: + lg(">> %s" % r) + language.serve(r, self.wfile, self.settings) + self.wfile.flush() + + def websocket_start(self, r): + """ + Performs an HTTP request, and attempts to drop into websocket + connection. + """ + resp = self.http(r) + if resp.status_code == 101: + self.ws_framereader = WebsocketFrameReader( + self.rfile, + self.fp, + self.showresp, + self.hexdump, + self.ws_read_limit, + self.timeout + ) + self.ws_framereader.start() + return resp + + def http(self, r): + """ + Performs a single request. + + r: A language.http.Request object, or a string representing one + request. + + Returns Response if we have a non-ignored response. + + May raise a NetlibException + """ + logger = log.ConnectionLogger( + self.fp, + self.hexdump, + self.rfile if self.showresp else None, + self.wfile if self.showreq else None, + ) + with logger.ctx() as lg: + lg(">> %s" % r) + resp, req = None, None + try: + req = language.serve(r, self.wfile, self.settings) + self.wfile.flush() + + resp = self.protocol.read_response(self.rfile, treq(method=req["method"].encode())) + resp.sslinfo = self.sslinfo + except HttpException as v: + lg("Invalid server response: %s" % v) + raise + except TcpTimeout: + if self.ignoretimeout: + lg("Timeout (ignored)") + return None + lg("Timeout") + raise + finally: + if resp: + lg("<< %s %s: %s bytes" % ( + resp.status_code, utils.xrepr(resp.msg), len(resp.content) + )) + if resp.status_code in self.ignorecodes: + lg.suppress() + return resp + + def request(self, r): + """ + Performs a single request. + + r: A language.message.Messsage object, or a string representing + one. + + Returns Response if we have a non-ignored response. + + May raise a NetlibException + """ + if isinstance(r, basestring): + r = language.parse_pathoc(r, self.use_http2).next() + + if isinstance(r, language.http.Request): + if r.ws: + return self.websocket_start(r) + else: + return self.http(r) + elif isinstance(r, language.websockets.WebsocketFrame): + self.websocket_send_frame(r) + elif isinstance(r, language.http2.Request): + return self.http(r) + # elif isinstance(r, language.http2.Frame): + # TODO: do something + + +def main(args): # pragma: nocover + memo = set([]) + trycount = 0 + p = None + try: + cnt = 0 + while True: + if cnt == args.repeat and args.repeat != 0: + break + if args.wait and cnt != 0: + time.sleep(args.wait) + + cnt += 1 + playlist = itertools.chain(*args.requests) + if args.random: + playlist = random.choice(args.requests) + p = Pathoc( + (args.host, args.port), + ssl=args.ssl, + sni=args.sni, + ssl_version=args.ssl_version, + ssl_options=args.ssl_options, + clientcert=args.clientcert, + ciphers=args.ciphers, + use_http2=args.use_http2, + http2_skip_connection_preface=args.http2_skip_connection_preface, + http2_framedump=args.http2_framedump, + showreq=args.showreq, + showresp=args.showresp, + explain=args.explain, + hexdump=args.hexdump, + ignorecodes=args.ignorecodes, + timeout=args.timeout, + ignoretimeout=args.ignoretimeout, + showsummary=True + ) + trycount = 0 + try: + p.connect(args.connect_to, args.showssl) + except TcpException as v: + print >> sys.stderr, str(v) + continue + except PathocError as v: + print >> sys.stderr, str(v) + sys.exit(1) + for spec in playlist: + if args.explain or args.memo: + spec = spec.freeze(p.settings) + if args.memo: + h = hashlib.sha256(spec.spec()).digest() + if h not in memo: + trycount = 0 + memo.add(h) + else: + trycount += 1 + if trycount > args.memolimit: + print >> sys.stderr, "Memo limit exceeded..." + return + else: + continue + try: + ret = p.request(spec) + if ret and args.oneshot: + return + # We consume the queue when we can, so it doesn't build up. + for i_ in p.wait(timeout=0, finish=False): + pass + except NetlibException: + break + for i_ in p.wait(timeout=0.01, finish=True): + pass + except KeyboardInterrupt: + pass + if p: + p.stop() diff --git a/pathod/libpathod/pathoc_cmdline.py b/pathod/libpathod/pathoc_cmdline.py new file mode 100644 index 00000000..bf827a9a --- /dev/null +++ b/pathod/libpathod/pathoc_cmdline.py @@ -0,0 +1,226 @@ +import sys +import argparse +import os +import os.path + +from netlib import tcp +from netlib.http import user_agents +from . import pathoc, version, language + + +def args_pathoc(argv, stdout=sys.stdout, stderr=sys.stderr): + preparser = argparse.ArgumentParser(add_help=False) + preparser.add_argument( + "--show-uas", dest="showua", action="store_true", default=False, + help="Print user agent shortcuts and exit." + ) + pa = preparser.parse_known_args(argv)[0] + if pa.showua: + print >> stdout, "User agent strings:" + for i in user_agents.UASTRINGS: + print >> stdout, " ", i[1], i[0] + sys.exit(0) + + parser = argparse.ArgumentParser( + description='A perverse HTTP client.', parents=[preparser] + ) + parser.add_argument( + '--version', + action='version', + version="pathoc " + version.VERSION + ) + parser.add_argument( + "-c", dest="connect_to", type=str, default=False, + metavar="HOST:PORT", + help="Issue an HTTP CONNECT to connect to the specified host." + ) + parser.add_argument( + "--memo-limit", dest='memolimit', default=5000, type=int, metavar="N", + help='Stop if we do not find a valid request after N attempts.' + ) + parser.add_argument( + "-m", dest='memo', action="store_true", default=False, + help=""" + Remember specs, and never play the same one twice. Note that this + means requests have to be rendered in memory, which means that + large generated data can cause issues. + """ + ) + parser.add_argument( + "-n", dest='repeat', default=1, type=int, metavar="N", + help='Repeat N times. If 0 repeat for ever.' + ) + parser.add_argument( + "-w", dest='wait', default=0, type=float, metavar="N", + help='Wait N seconds between each request.' + ) + parser.add_argument( + "-r", dest="random", action="store_true", default=False, + help=""" + Select a random request from those specified. If this is not specified, + requests are all played in sequence. + """ + ) + parser.add_argument( + "-t", dest="timeout", type=int, default=None, + help="Connection timeout" + ) + parser.add_argument( + "--http2", dest="use_http2", action="store_true", default=False, + help='Perform all requests over a single HTTP/2 connection.' + ) + parser.add_argument( + "--http2-skip-connection-preface", + dest="http2_skip_connection_preface", + action="store_true", + default=False, + help='Skips the HTTP/2 connection preface before sending requests.') + + parser.add_argument( + 'host', type=str, + metavar="host[:port]", + help='Host and port to connect to' + ) + parser.add_argument( + 'requests', type=str, nargs="+", + help=""" + Request specification, or path to a file containing request + specifcations + """ + ) + + group = parser.add_argument_group( + 'SSL', + ) + group.add_argument( + "-s", dest="ssl", action="store_true", default=False, + help="Connect with SSL" + ) + group.add_argument( + "-C", dest="clientcert", type=str, default=False, + help="Path to a file containing client certificate and private key" + ) + group.add_argument( + "-i", dest="sni", type=str, default=False, + help="SSL Server Name Indication" + ) + group.add_argument( + "--ciphers", dest="ciphers", type=str, default=False, + help="SSL cipher specification" + ) + group.add_argument( + "--ssl-version", dest="ssl_version", type=str, default="secure", + choices=tcp.sslversion_choices.keys(), + help="Set supported SSL/TLS versions. " + "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure, which is TLS1.0+." + ) + + group = parser.add_argument_group( + 'Controlling Output', + """ + Some of these options expand generated values for logging - if + you're generating large data, use them with caution. + """ + ) + group.add_argument( + "-I", dest="ignorecodes", type=str, default="", + help="Comma-separated list of response codes to ignore" + ) + group.add_argument( + "-S", dest="showssl", action="store_true", default=False, + help="Show info on SSL connection" + ) + group.add_argument( + "-e", dest="explain", action="store_true", default=False, + help="Explain requests" + ) + group.add_argument( + "-o", dest="oneshot", action="store_true", default=False, + help="Oneshot - exit after first non-ignored response" + ) + group.add_argument( + "-q", dest="showreq", action="store_true", default=False, + help="Print full request" + ) + group.add_argument( + "-p", dest="showresp", action="store_true", default=False, + help="Print full response" + ) + group.add_argument( + "-T", dest="ignoretimeout", action="store_true", default=False, + help="Ignore timeouts" + ) + group.add_argument( + "-x", dest="hexdump", action="store_true", default=False, + help="Output in hexdump format" + ) + group.add_argument( + "--http2-framedump", dest="http2_framedump", action="store_true", default=False, + help="Output all received & sent HTTP/2 frames" + ) + + args = parser.parse_args(argv[1:]) + + args.ssl_version, args.ssl_options = tcp.sslversion_choices[args.ssl_version] + + args.port = None + if ":" in args.host: + h, p = args.host.rsplit(":", 1) + try: + p = int(p) + except ValueError: + return parser.error("Invalid port in host spec: %s" % args.host) + args.host = h + args.port = p + + if args.port is None: + args.port = 443 if args.ssl else 80 + + try: + args.ignorecodes = [int(i) for i in args.ignorecodes.split(",") if i] + except ValueError: + return parser.error( + "Invalid return code specification: %s" % + args.ignorecodes) + + if args.connect_to: + parts = args.connect_to.split(":") + if len(parts) != 2: + return parser.error( + "Invalid CONNECT specification: %s" % + args.connect_to) + try: + parts[1] = int(parts[1]) + except ValueError: + return parser.error( + "Invalid CONNECT specification: %s" % + args.connect_to) + args.connect_to = parts + else: + args.connect_to = None + + if args.http2_skip_connection_preface: + args.use_http2 = True + + if args.use_http2: + args.ssl = True + + reqs = [] + for r in args.requests: + if os.path.isfile(r): + data = open(r).read() + r = data + try: + reqs.append(language.parse_pathoc(r, args.use_http2)) + except language.ParseException as v: + print >> stderr, "Error parsing request spec: %s" % v.msg + print >> stderr, v.marked() + sys.exit(1) + args.requests = reqs + + return args + + +def go_pathoc(): # pragma: nocover + args = args_pathoc(sys.argv) + pathoc.main(args) diff --git a/pathod/libpathod/pathod.py b/pathod/libpathod/pathod.py new file mode 100644 index 00000000..55e75074 --- /dev/null +++ b/pathod/libpathod/pathod.py @@ -0,0 +1,503 @@ +import copy +import logging +import os +import sys +import threading +import urllib + +from netlib import tcp, http, certutils, websockets +from netlib.exceptions import HttpException, HttpReadDisconnect, TcpTimeout, TcpDisconnect, \ + TlsException + +from . import version, app, language, utils, log, protocols +import language.http +import language.actions +import language.exceptions +import language.websockets + + +DEFAULT_CERT_DOMAIN = "pathod.net" +CONFDIR = "~/.mitmproxy" +CERTSTORE_BASENAME = "mitmproxy" +CA_CERT_NAME = "mitmproxy-ca.pem" +DEFAULT_CRAFT_ANCHOR = "/p/" + +logger = logging.getLogger('pathod') + + +class PathodError(Exception): + pass + + +class SSLOptions(object): + def __init__( + self, + confdir=CONFDIR, + cn=None, + sans=(), + not_after_connect=None, + request_client_cert=False, + ssl_version=tcp.SSL_DEFAULT_METHOD, + ssl_options=tcp.SSL_DEFAULT_OPTIONS, + ciphers=None, + certs=None, + alpn_select=b'h2', + ): + self.confdir = confdir + self.cn = cn + self.sans = sans + self.not_after_connect = not_after_connect + self.request_client_cert = request_client_cert + self.ssl_version = ssl_version + self.ssl_options = ssl_options + self.ciphers = ciphers + self.alpn_select = alpn_select + self.certstore = certutils.CertStore.from_store( + os.path.expanduser(confdir), + CERTSTORE_BASENAME + ) + for i in certs or []: + self.certstore.add_cert_file(*i) + + def get_cert(self, name): + if self.cn: + name = self.cn + elif not name: + name = DEFAULT_CERT_DOMAIN + return self.certstore.get_cert(name, self.sans) + + +class PathodHandler(tcp.BaseHandler): + wbufsize = 0 + sni = None + + def __init__( + self, + connection, + address, + server, + logfp, + settings, + http2_framedump=False + ): + tcp.BaseHandler.__init__(self, connection, address, server) + self.logfp = logfp + self.settings = copy.copy(settings) + self.protocol = None + self.use_http2 = False + self.http2_framedump = http2_framedump + + def handle_sni(self, connection): + self.sni = connection.get_servername() + + def http_serve_crafted(self, crafted, logctx): + error, crafted = self.server.check_policy( + crafted, self.settings + ) + if error: + err = self.make_http_error_response(error) + language.serve(err, self.wfile, self.settings) + return None, dict( + type="error", + msg=error + ) + + if self.server.explain and not hasattr(crafted, 'is_error_response'): + crafted = crafted.freeze(self.settings) + logctx(">> Spec: %s" % crafted.spec()) + + response_log = language.serve( + crafted, + self.wfile, + self.settings + ) + if response_log["disconnect"]: + return None, response_log + return self.handle_http_request, response_log + + + def handle_http_request(self, logger): + """ + Returns a (handler, log) tuple. + + handler: Handler for the next request, or None to disconnect + log: A dictionary, or None + """ + with logger.ctx() as lg: + try: + req = self.protocol.read_request(self.rfile) + except HttpReadDisconnect: + return None, None + except HttpException as s: + s = str(s) + lg(s) + return None, dict(type="error", msg=s) + + if req.method == 'CONNECT': + return self.protocol.handle_http_connect([req.host, req.port, req.http_version], lg) + + method = req.method + path = req.path + http_version = req.http_version + headers = req.headers + body = req.content + + clientcert = None + if self.clientcert: + clientcert = dict( + cn=self.clientcert.cn, + subject=self.clientcert.subject, + serial=self.clientcert.serial, + notbefore=self.clientcert.notbefore.isoformat(), + notafter=self.clientcert.notafter.isoformat(), + keyinfo=self.clientcert.keyinfo, + ) + + retlog = dict( + type="crafted", + protocol="http", + request=dict( + path=path, + method=method, + headers=headers.fields, + http_version=http_version, + sni=self.sni, + remote_address=self.address(), + clientcert=clientcert, + ), + cipher=None, + ) + if self.ssl_established: + retlog["cipher"] = self.get_current_cipher() + + m = utils.MemBool() + websocket_key = websockets.WebsocketsProtocol.check_client_handshake(headers) + self.settings.websocket_key = websocket_key + + # If this is a websocket initiation, we respond with a proper + # server response, unless over-ridden. + if websocket_key: + anchor_gen = language.parse_pathod("ws") + else: + anchor_gen = None + + for regex, spec in self.server.anchors: + if regex.match(path): + anchor_gen = language.parse_pathod(spec, self.use_http2) + break + else: + if m(path.startswith(self.server.craftanchor)): + spec = urllib.unquote(path)[len(self.server.craftanchor):] + if spec: + try: + anchor_gen = language.parse_pathod(spec, self.use_http2) + except language.ParseException as v: + lg("Parse error: %s" % v.msg) + anchor_gen = iter([self.make_http_error_response( + "Parse Error", + "Error parsing response spec: %s\n" % ( + v.msg + v.marked() + ) + )]) + else: + if self.use_http2: + anchor_gen = iter([self.make_http_error_response( + "Spec Error", + "HTTP/2 only supports request/response with the craft anchor point: %s" % + self.server.craftanchor + )]) + + if anchor_gen: + spec = anchor_gen.next() + + if self.use_http2 and isinstance(spec, language.http2.Response): + spec.stream_id = req.stream_id + + lg("crafting spec: %s" % spec) + nexthandler, retlog["response"] = self.http_serve_crafted( + spec, + lg + ) + if nexthandler and websocket_key: + self.protocol = protocols.websockets.WebsocketsProtocol(self) + return self.protocol.handle_websocket, retlog + else: + return nexthandler, retlog + else: + return self.protocol.handle_http_app(method, path, headers, body, lg) + + def make_http_error_response(self, reason, body=None): + resp = self.protocol.make_error_response(reason, body) + resp.is_error_response = True + return resp + + def handle(self): + self.settimeout(self.server.timeout) + + if self.server.ssl: + try: + cert, key, _ = self.server.ssloptions.get_cert(None) + self.convert_to_ssl( + cert, + key, + handle_sni=self.handle_sni, + request_client_cert=self.server.ssloptions.request_client_cert, + cipher_list=self.server.ssloptions.ciphers, + method=self.server.ssloptions.ssl_version, + options=self.server.ssloptions.ssl_options, + alpn_select=self.server.ssloptions.alpn_select, + ) + except TlsException as v: + s = str(v) + self.server.add_log( + dict( + type="error", + msg=s + ) + ) + log.write_raw(self.logfp, s) + return + + alp = self.get_alpn_proto_negotiated() + if alp == b'h2': + self.protocol = protocols.http2.HTTP2Protocol(self) + self.use_http2 = True + + if not self.protocol: + self.protocol = protocols.http.HTTPProtocol(self) + + lr = self.rfile if self.server.logreq else None + lw = self.wfile if self.server.logresp else None + logger = log.ConnectionLogger(self.logfp, self.server.hexdump, lr, lw) + + self.settings.protocol = self.protocol + + handler = self.handle_http_request + + while not self.finished: + handler, l = handler(logger) + if l: + self.addlog(l) + if not handler: + return + + def addlog(self, log): + # FIXME: The bytes in the log should not be escaped. We do this at the + # moment because JSON encoding can't handle binary data, and I don't + # want to base64 everything. + if self.server.logreq: + encoded_bytes = self.rfile.get_log().encode("string_escape") + log["request_bytes"] = encoded_bytes + if self.server.logresp: + encoded_bytes = self.wfile.get_log().encode("string_escape") + log["response_bytes"] = encoded_bytes + self.server.add_log(log) + + +class Pathod(tcp.TCPServer): + LOGBUF = 500 + + def __init__( + self, + addr, + ssl=False, + ssloptions=None, + craftanchor=DEFAULT_CRAFT_ANCHOR, + staticdir=None, + anchors=(), + sizelimit=None, + noweb=False, + nocraft=False, + noapi=False, + nohang=False, + timeout=None, + logreq=False, + logresp=False, + explain=False, + hexdump=False, + http2_framedump=False, + webdebug=False, + logfp=sys.stdout, + ): + """ + addr: (address, port) tuple. If port is 0, a free port will be + automatically chosen. + ssloptions: an SSLOptions object. + craftanchor: URL prefix specifying the path under which to anchor + response generation. + staticdir: path to a directory of static resources, or None. + anchors: List of (regex object, language.Request object) tuples, or + None. + sizelimit: Limit size of served data. + nocraft: Disable response crafting. + noapi: Disable the API. + nohang: Disable pauses. + """ + tcp.TCPServer.__init__(self, addr) + self.ssl = ssl + self.ssloptions = ssloptions or SSLOptions() + self.staticdir = staticdir + self.craftanchor = craftanchor + self.sizelimit = sizelimit + self.noweb, self.nocraft = noweb, nocraft + self.noapi, self.nohang = noapi, nohang + self.timeout, self.logreq = timeout, logreq + self.logresp, self.hexdump = logresp, hexdump + self.http2_framedump = http2_framedump + self.explain = explain + self.logfp = logfp + + self.app = app.make_app(noapi, webdebug) + self.app.config["pathod"] = self + self.log = [] + self.logid = 0 + self.anchors = anchors + + self.settings = language.Settings( + staticdir=self.staticdir + ) + + def check_policy(self, req, settings): + """ + A policy check that verifies the request size is within limits. + """ + if self.nocraft: + return "Crafting disabled.", None + try: + req = req.resolve(settings) + l = req.maximum_length(settings) + except language.FileAccessDenied: + return "File access denied.", None + if self.sizelimit and l > self.sizelimit: + return "Response too large.", None + pauses = [isinstance(i, language.actions.PauseAt) for i in req.actions] + if self.nohang and any(pauses): + return "Pauses have been disabled.", None + return None, req + + def handle_client_connection(self, request, client_address): + h = PathodHandler( + request, + client_address, + self, + self.logfp, + self.settings, + self.http2_framedump, + ) + try: + h.handle() + h.finish() + except TcpDisconnect: # pragma: no cover + log.write_raw(self.logfp, "Disconnect") + self.add_log( + dict( + type="error", + msg="Disconnect" + ) + ) + return + except TcpTimeout: + log.write_raw(self.logfp, "Timeout") + self.add_log( + dict( + type="timeout", + ) + ) + return + + def add_log(self, d): + if not self.noapi: + lock = threading.Lock() + with lock: + d["id"] = self.logid + self.log.insert(0, d) + if len(self.log) > self.LOGBUF: + self.log.pop() + self.logid += 1 + return d["id"] + + def clear_log(self): + lock = threading.Lock() + with lock: + self.log = [] + + def log_by_id(self, identifier): + for i in self.log: + if i["id"] == identifier: + return i + + def get_log(self): + return self.log + + +def main(args): # pragma: nocover + ssloptions = SSLOptions( + cn=args.cn, + confdir=args.confdir, + not_after_connect=args.ssl_not_after_connect, + ciphers=args.ciphers, + ssl_version=args.ssl_version, + ssl_options=args.ssl_options, + certs=args.ssl_certs, + sans=args.sans, + ) + + root = logging.getLogger() + if root.handlers: + for handler in root.handlers: + root.removeHandler(handler) + + log = logging.getLogger('pathod') + log.setLevel(logging.DEBUG) + fmt = logging.Formatter( + '%(asctime)s: %(message)s', + datefmt='%d-%m-%y %H:%M:%S', + ) + if args.logfile: + fh = logging.handlers.WatchedFileHandler(args.logfile) + fh.setFormatter(fmt) + log.addHandler(fh) + if not args.daemonize: + sh = logging.StreamHandler() + sh.setFormatter(fmt) + log.addHandler(sh) + + try: + pd = Pathod( + (args.address, args.port), + craftanchor=args.craftanchor, + ssl=args.ssl, + ssloptions=ssloptions, + staticdir=args.staticdir, + anchors=args.anchors, + sizelimit=args.sizelimit, + noweb=args.noweb, + nocraft=args.nocraft, + noapi=args.noapi, + nohang=args.nohang, + timeout=args.timeout, + logreq=args.logreq, + logresp=args.logresp, + hexdump=args.hexdump, + http2_framedump=args.http2_framedump, + explain=args.explain, + webdebug=args.webdebug + ) + except PathodError as v: + print >> sys.stderr, "Error: %s" % v + sys.exit(1) + except language.FileAccessDenied as v: + print >> sys.stderr, "Error: %s" % v + + if args.daemonize: + utils.daemonize() + + try: + print "%s listening on %s:%s" % ( + version.NAMEVERSION, + pd.address.host, + pd.address.port + ) + pd.serve_forever() + except KeyboardInterrupt: + pass diff --git a/pathod/libpathod/pathod_cmdline.py b/pathod/libpathod/pathod_cmdline.py new file mode 100644 index 00000000..c9272249 --- /dev/null +++ b/pathod/libpathod/pathod_cmdline.py @@ -0,0 +1,231 @@ +import sys +import argparse +import os +import os.path +import re + +from netlib import tcp +from . import pathod, version, utils + + +def args_pathod(argv, stdout_=sys.stdout, stderr_=sys.stderr): + parser = argparse.ArgumentParser( + description='A pathological HTTP/S daemon.' + ) + parser.add_argument( + '--version', + action='version', + version="pathod " + version.VERSION + ) + parser.add_argument( + "-p", + dest='port', + default=9999, + type=int, + help='Port. Specify 0 to pick an arbitrary empty port. (9999)' + ) + parser.add_argument( + "-l", + dest='address', + default="127.0.0.1", + type=str, + help='Listening address. (127.0.0.1)' + ) + parser.add_argument( + "-a", + dest='anchors', + default=[], + type=str, + action="append", + metavar="ANCHOR", + help=""" + Add an anchor. Specified as a string with the form + pattern=spec or pattern=filepath, where pattern is a regular + expression. + """ + ) + parser.add_argument( + "-c", dest='craftanchor', default=pathod.DEFAULT_CRAFT_ANCHOR, type=str, + help=""" + URL path specifying prefix for URL crafting + commands. (%s) + """%pathod.DEFAULT_CRAFT_ANCHOR + ) + parser.add_argument( + "--confdir", + action="store", type = str, dest="confdir", default='~/.mitmproxy', + help = "Configuration directory. (~/.mitmproxy)" + ) + parser.add_argument( + "-d", dest='staticdir', default=None, type=str, + help='Directory for static files.' + ) + parser.add_argument( + "-D", dest='daemonize', default=False, action="store_true", + help='Daemonize.' + ) + parser.add_argument( + "-t", dest="timeout", type=int, default=None, + help="Connection timeout" + ) + parser.add_argument( + "--limit-size", + dest='sizelimit', + default=None, + type=str, + help='Size limit of served responses. Understands size suffixes, i.e. 100k.') + parser.add_argument( + "--noapi", dest='noapi', default=False, action="store_true", + help='Disable API.' + ) + parser.add_argument( + "--nohang", dest='nohang', default=False, action="store_true", + help='Disable pauses during crafted response generation.' + ) + parser.add_argument( + "--noweb", dest='noweb', default=False, action="store_true", + help='Disable both web interface and API.' + ) + parser.add_argument( + "--nocraft", + dest='nocraft', + default=False, + action="store_true", + help='Disable response crafting. If anchors are specified, they still work.') + parser.add_argument( + "--webdebug", dest='webdebug', default=False, action="store_true", + help='Debugging mode for the web app (dev only).' + ) + + group = parser.add_argument_group( + 'SSL', + ) + group.add_argument( + "-s", dest='ssl', default=False, action="store_true", + help='Run in HTTPS mode.' + ) + group.add_argument( + "--cn", + dest="cn", + type=str, + default=None, + help="CN for generated SSL certs. Default: %s" % + pathod.DEFAULT_CERT_DOMAIN) + group.add_argument( + "-C", dest='ssl_not_after_connect', default=False, action="store_true", + help="Don't expect SSL after a CONNECT request." + ) + group.add_argument( + "--cert", dest='ssl_certs', default=[], type=str, + metavar = "SPEC", action="append", + help = """ + Add an SSL certificate. SPEC is of the form "[domain=]path". The domain + may include a wildcard, and is equal to "*" if not specified. The file + at path is a certificate in PEM format. If a private key is included in + the PEM, it is used, else the default key in the conf dir is used. Can + be passed multiple times. + """ + ) + group.add_argument( + "--ciphers", dest="ciphers", type=str, default=False, + help="SSL cipher specification" + ) + group.add_argument( + "--san", dest="sans", type=str, default=[], action="append", + metavar="SAN", + help=""" + Subject Altnernate Name to add to the server certificate. + May be passed multiple times. + """ + ) + group.add_argument( + "--ssl-version", dest="ssl_version", type=str, default="secure", + choices=tcp.sslversion_choices.keys(), + help="Set supported SSL/TLS versions. " + "SSLv2, SSLv3 and 'all' are INSECURE. Defaults to secure, which is TLS1.0+." + ) + + group = parser.add_argument_group( + 'Controlling Logging', + """ + Some of these options expand generated values for logging - if + you're generating large data, use them with caution. + """ + ) + group.add_argument( + "-e", dest="explain", action="store_true", default=False, + help="Explain responses" + ) + group.add_argument( + "-f", dest='logfile', default=None, type=str, + help='Log to file.' + ) + group.add_argument( + "-q", dest="logreq", action="store_true", default=False, + help="Log full request" + ) + group.add_argument( + "-r", dest="logresp", action="store_true", default=False, + help="Log full response" + ) + group.add_argument( + "-x", dest="hexdump", action="store_true", default=False, + help="Log request/response in hexdump format" + ) + group.add_argument( + "--http2-framedump", dest="http2_framedump", action="store_true", default=False, + help="Output all received & sent HTTP/2 frames" + ) + + + args = parser.parse_args(argv[1:]) + + args.ssl_version, args.ssl_options = tcp.sslversion_choices[args.ssl_version] + + certs = [] + for i in args.ssl_certs: + parts = i.split("=", 1) + if len(parts) == 1: + parts = ["*", parts[0]] + parts[1] = os.path.expanduser(parts[1]) + if not os.path.isfile(parts[1]): + return parser.error( + "Certificate file does not exist: %s" % + parts[1]) + certs.append(parts) + args.ssl_certs = certs + + alst = [] + for i in args.anchors: + parts = utils.parse_anchor_spec(i) + if not parts: + return parser.error("Invalid anchor specification: %s" % i) + alst.append(parts) + args.anchors = alst + + sizelimit = None + if args.sizelimit: + try: + sizelimit = utils.parse_size(args.sizelimit) + except ValueError as v: + return parser.error(v) + args.sizelimit = sizelimit + + anchors = [] + for patt, spec in args.anchors: + if os.path.isfile(spec): + data = open(spec).read() + spec = data + try: + arex = re.compile(patt) + except re.error: + return parser.error("Invalid regex in anchor: %s" % patt) + anchors.append((arex, spec)) + args.anchors = anchors + + return args + + +def go_pathod(): # pragma: nocover + args = args_pathod(sys.argv) + pathod.main(args) diff --git a/pathod/libpathod/protocols/__init__.py b/pathod/libpathod/protocols/__init__.py new file mode 100644 index 00000000..1a8c7dab --- /dev/null +++ b/pathod/libpathod/protocols/__init__.py @@ -0,0 +1 @@ +from . import http, http2, websockets diff --git a/pathod/libpathod/protocols/http.py b/pathod/libpathod/protocols/http.py new file mode 100644 index 00000000..1f1765cb --- /dev/null +++ b/pathod/libpathod/protocols/http.py @@ -0,0 +1,71 @@ +from netlib import tcp, wsgi +from netlib.exceptions import HttpReadDisconnect, TlsException +from netlib.http import http1, Request +from .. import version, language + + +class HTTPProtocol(object): + def __init__(self, pathod_handler): + self.pathod_handler = pathod_handler + + def make_error_response(self, reason, body): + return language.http.make_error_response(reason, body) + + def handle_http_app(self, method, path, headers, body, lg): + """ + Handle a request to the built-in app. + """ + if self.pathod_handler.server.noweb: + crafted = self.pathod_handler.make_http_error_response("Access Denied") + language.serve(crafted, self.pathod_handler.wfile, self.pathod_handler.settings) + return None, dict( + type="error", + msg="Access denied: web interface disabled" + ) + lg("app: %s %s" % (method, path)) + req = wsgi.Request("http", method, path, b"HTTP/1.1", headers, body) + flow = wsgi.Flow(self.pathod_handler.address, req) + sn = self.pathod_handler.connection.getsockname() + a = wsgi.WSGIAdaptor( + self.pathod_handler.server.app, + sn[0], + self.pathod_handler.server.address.port, + version.NAMEVERSION + ) + a.serve(flow, self.pathod_handler.wfile) + return self.pathod_handler.handle_http_request, None + + def handle_http_connect(self, connect, lg): + """ + Handle a CONNECT request. + """ + + self.pathod_handler.wfile.write( + 'HTTP/1.1 200 Connection established\r\n' + + ('Proxy-agent: %s\r\n' % version.NAMEVERSION) + + '\r\n' + ) + self.pathod_handler.wfile.flush() + if not self.pathod_handler.server.ssloptions.not_after_connect: + try: + cert, key, chain_file_ = self.pathod_handler.server.ssloptions.get_cert( + connect[0] + ) + self.pathod_handler.convert_to_ssl( + cert, + key, + handle_sni=self.pathod_handler.handle_sni, + request_client_cert=self.pathod_handler.server.ssloptions.request_client_cert, + cipher_list=self.pathod_handler.server.ssloptions.ciphers, + method=self.pathod_handler.server.ssloptions.ssl_version, + options=self.pathod_handler.server.ssloptions.ssl_options, + alpn_select=self.pathod_handler.server.ssloptions.alpn_select, + ) + except TlsException as v: + s = str(v) + lg(s) + return None, dict(type="error", msg=s) + return self.pathod_handler.handle_http_request, None + + def read_request(self, lg=None): + return http1.read_request(self.pathod_handler.rfile) diff --git a/pathod/libpathod/protocols/http2.py b/pathod/libpathod/protocols/http2.py new file mode 100644 index 00000000..a098a14e --- /dev/null +++ b/pathod/libpathod/protocols/http2.py @@ -0,0 +1,20 @@ +from netlib.http import http2 +from .. import version, app, language, utils, log + +class HTTP2Protocol: + + def __init__(self, pathod_handler): + self.pathod_handler = pathod_handler + self.wire_protocol = http2.HTTP2Protocol( + self.pathod_handler, is_server=True, dump_frames=self.pathod_handler.http2_framedump + ) + + def make_error_response(self, reason, body): + return language.http2.make_error_response(reason, body) + + def read_request(self, lg=None): + self.wire_protocol.perform_server_connection_preface() + return self.wire_protocol.read_request(self.pathod_handler.rfile) + + def assemble(self, message): + return self.wire_protocol.assemble(message) diff --git a/pathod/libpathod/protocols/websockets.py b/pathod/libpathod/protocols/websockets.py new file mode 100644 index 00000000..134d27bc --- /dev/null +++ b/pathod/libpathod/protocols/websockets.py @@ -0,0 +1,56 @@ +import time + +from netlib import websockets +from .. import language +from netlib.exceptions import NetlibException + + +class WebsocketsProtocol: + + def __init__(self, pathod_handler): + self.pathod_handler = pathod_handler + + def handle_websocket(self, logger): + while True: + with logger.ctx() as lg: + started = time.time() + try: + frm = websockets.Frame.from_file(self.pathod_handler.rfile) + except NetlibException as e: + lg("Error reading websocket frame: %s" % e) + break + ended = time.time() + lg(frm.human_readable()) + retlog = dict( + type="inbound", + protocol="websockets", + started=started, + duration=ended - started, + frame=dict( + ), + cipher=None, + ) + if self.pathod_handler.ssl_established: + retlog["cipher"] = self.pathod_handler.get_current_cipher() + self.pathod_handler.addlog(retlog) + ld = language.websockets.NESTED_LEADER + if frm.payload.startswith(ld): + nest = frm.payload[len(ld):] + try: + wf_gen = language.parse_websocket_frame(nest) + except language.exceptions.ParseException as v: + logger.write( + "Parse error in reflected frame specifcation:" + " %s" % v.msg + ) + return None, None + for frm in wf_gen: + with logger.ctx() as lg: + frame_log = language.serve( + frm, + self.pathod_handler.wfile, + self.pathod_handler.settings + ) + lg("crafting websocket spec: %s" % frame_log["spec"]) + self.pathod_handler.addlog(frame_log) + return self.handle_websocket, None diff --git a/pathod/libpathod/static/bootstrap.min.css b/pathod/libpathod/static/bootstrap.min.css new file mode 100644 index 00000000..2e79d91a --- /dev/null +++ b/pathod/libpathod/static/bootstrap.min.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap v2.3.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:32px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;line-height:22px;color:#555;background-color:#fff}a{color:#007fff;text-decoration:none}a:hover,a:focus{color:#06c;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:32px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 11px}.lead{margin-bottom:22px;font-size:24px;font-weight:200;line-height:33px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#dfdfdf}a.muted:hover,a.muted:focus{color:#c6c6c6}.text-warning{color:#fff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-error{color:#fff}a.text-error:hover,a.text-error:focus{color:#e6e6e6}.text-info{color:#fff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-success{color:#fff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:11px 0;font-family:inherit;font-weight:300;line-height:22px;color:#080808;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#dfdfdf}h1,h2,h3{line-height:44px}h1{font-size:44px}h2{font-size:36px}h3{font-size:28px}h4{font-size:20px}h5{font-size:16px}h6{font-size:13.6px}h1 small{font-size:28px}h2 small{font-size:20px}h3 small{font-size:16px}h4 small{font-size:16px}.page-header{padding-bottom:10px;margin:44px 0 22px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 11px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:22px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:22px}dt,dd{line-height:22px}dt{font-weight:bold}dd{margin-left:11px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:22px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #dfdfdf}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 22px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:20px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:22px;color:#dfdfdf}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:22px;font-style:normal;line-height:22px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:14px;color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:10.5px;margin:0 0 11px;font-size:15px;line-height:22px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}pre.prettyprint{margin-bottom:22px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 22px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:22px;font-size:24px;line-height:44px;color:#999;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:16.5px;color:#dfdfdf}label,input,button,select,textarea{font-size:16px;font-weight:normal;line-height:22px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:22px;padding:4px 6px;margin-bottom:11px;font-size:16px;line-height:22px;color:#bbb;vertical-align:middle;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #bbb;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:32px;*margin-top:4px;line-height:32px}select{width:220px;background-color:#fff;border:1px solid #bbb}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#dfdfdf;cursor:not-allowed;background-color:#fcfcfc;border-color:#bbb;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#bbb}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#bbb}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#bbb}.radio,.checkbox{min-height:22px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#fff}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#fff}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#fff;background-color:#ff7518;border-color:#fff}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#fff}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#fff}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#fff;background-color:#ff0039;border-color:#fff}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#fff}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#fff}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#fff;background-color:#3fb618;border-color:#fff}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#fff}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#fff}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#fff;background-color:#9954bb;border-color:#fff}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:21px 20px 22px;margin-top:22px;margin-bottom:22px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#7b7b7b}.help-block{display:block;margin-bottom:11px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:11px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:16px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:22px;min-width:16px;padding:4px 5px;font-size:16px;font-weight:normal;line-height:22px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#96ed7a;border-color:#3fb618}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:11px}legend+.control-group{margin-top:22px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:22px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:11px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:22px}.table th,.table td{padding:8px;line-height:22px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-topleft:0}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:0;border-top-right-radius:0;-moz-border-radius-topright:0}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-topleft:0}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:0;border-top-right-radius:0;-moz-border-radius-topright:0}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#e8f8fd}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#3fb618}.table tbody tr.error>td{background-color:#ff0039}.table tbody tr.warning>td{background-color:#ff7518}.table tbody tr.info>td{background-color:#9954bb}.table-hover tbody tr.success:hover>td{background-color:#379f15}.table-hover tbody tr.error:hover>td{background-color:#e60033}.table-hover tbody tr.warning:hover>td{background-color:#fe6600}.table-hover tbody tr.info:hover>td{background-color:#8d46b0}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:10px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:22px;color:#999;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#007af5;background-image:-moz-linear-gradient(top,#007fff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#0072e6));background-image:-webkit-linear-gradient(top,#007fff,#0072e6);background-image:-o-linear-gradient(top,#007fff,#0072e6);background-image:linear-gradient(to bottom,#007fff,#0072e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff0072e6',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#007af5;background-image:-moz-linear-gradient(top,#007fff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#0072e6));background-image:-webkit-linear-gradient(top,#007fff,#0072e6);background-image:-o-linear-gradient(top,#007fff,#0072e6);background-image:linear-gradient(to bottom,#007fff,#0072e6);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff0072e6',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#dfdfdf}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#eee;border:1px solid #dcdcdc;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.well-small{padding:9px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:22px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:16px;line-height:22px;color:#999;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#dfdfdf;*background-color:#c8c8c8;background-image:-moz-linear-gradient(top,#eee,#c8c8c8);background-image:-webkit-gradient(linear,0 0,0 100%,from(#eee),to(#c8c8c8));background-image:-webkit-linear-gradient(top,#eee,#c8c8c8);background-image:-o-linear-gradient(top,#eee,#c8c8c8);background-image:linear-gradient(to bottom,#eee,#c8c8c8);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#c8c8c8 #c8c8c8 #a2a2a2;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffeeeeee',endColorstr='#ffc8c8c8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#999;background-color:#c8c8c8;*background-color:#bbb}.btn:active,.btn.active{background-color:#aeaeae \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#999;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:22px 30px;font-size:20px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:13.6px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:2px 6px;font-size:12px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0f82f5;*background-color:#0072e6;background-image:-moz-linear-gradient(top,#1a8cff,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#1a8cff),to(#0072e6));background-image:-webkit-linear-gradient(top,#1a8cff,#0072e6);background-image:-o-linear-gradient(top,#1a8cff,#0072e6);background-image:linear-gradient(to bottom,#1a8cff,#0072e6);background-repeat:repeat-x;border-color:#0072e6 #0072e6 #004c99;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1a8cff',endColorstr='#ff0072e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#0072e6;*background-color:#06c}.btn-primary:active,.btn-primary.active{background-color:#0059b3 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#fe781e;*background-color:#fe6600;background-image:-moz-linear-gradient(top,#ff8432,#fe6600);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff8432),to(#fe6600));background-image:-webkit-linear-gradient(top,#ff8432,#fe6600);background-image:-o-linear-gradient(top,#ff8432,#fe6600);background-image:linear-gradient(to bottom,#ff8432,#fe6600);background-repeat:repeat-x;border-color:#fe6600 #fe6600 #b14700;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff8432',endColorstr='#fffe6600',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#fe6600;*background-color:#e45c00}.btn-warning:active,.btn-warning.active{background-color:#cb5200 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#f50f43;*background-color:#e60033;background-image:-moz-linear-gradient(top,#ff1a4d,#e60033);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff1a4d),to(#e60033));background-image:-webkit-linear-gradient(top,#ff1a4d,#e60033);background-image:-o-linear-gradient(top,#ff1a4d,#e60033);background-image:linear-gradient(to bottom,#ff1a4d,#e60033);background-repeat:repeat-x;border-color:#e60033 #e60033 #902;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff1a4d',endColorstr='#ffe60033',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#e60033;*background-color:#cc002e}.btn-danger:active,.btn-danger.active{background-color:#b30028 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#41bb19;*background-color:#379f15;background-image:-moz-linear-gradient(top,#47cd1b,#379f15);background-image:-webkit-gradient(linear,0 0,0 100%,from(#47cd1b),to(#379f15));background-image:-webkit-linear-gradient(top,#47cd1b,#379f15);background-image:-o-linear-gradient(top,#47cd1b,#379f15);background-image:linear-gradient(to bottom,#47cd1b,#379f15);background-repeat:repeat-x;border-color:#379f15 #379f15 #205c0c;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff47cd1b',endColorstr='#ff379f15',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#379f15;*background-color:#2f8912}.btn-success:active,.btn-success.active{background-color:#28720f \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#9b59bb;*background-color:#8d46b0;background-image:-moz-linear-gradient(top,#a466c2,#8d46b0);background-image:-webkit-gradient(linear,0 0,0 100%,from(#a466c2),to(#8d46b0));background-image:-webkit-linear-gradient(top,#a466c2,#8d46b0);background-image:-o-linear-gradient(top,#a466c2,#8d46b0);background-image:linear-gradient(to bottom,#a466c2,#8d46b0);background-repeat:repeat-x;border-color:#8d46b0 #8d46b0 #613079;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa466c2',endColorstr='#ff8d46b0',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#8d46b0;*background-color:#7e3f9d}.btn-info:active,.btn-info.active{background-color:#6f378b \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#080808;*background-color:#000;background-image:-moz-linear-gradient(top,#0d0d0d,#000);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0d0d0d),to(#000));background-image:-webkit-linear-gradient(top,#0d0d0d,#000);background-image:-o-linear-gradient(top,#0d0d0d,#000);background-image:linear-gradient(to bottom,#0d0d0d,#000);background-repeat:repeat-x;border-color:#000 #000 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0d0d0d',endColorstr='#ff000000',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#000;*background-color:#000}.btn-inverse:active,.btn-inverse.active{background-color:#000 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#007fff;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#06c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#999;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:11px;margin-bottom:11px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:16px}.btn-group>.btn-mini{font-size:12px}.btn-group>.btn-small{font-size:13.6px}.btn-group>.btn-large{font-size:20px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#c8c8c8}.btn-group.open .btn-primary.dropdown-toggle{background-color:#0072e6}.btn-group.open .btn-warning.dropdown-toggle{background-color:#fe6600}.btn-group.open .btn-danger.dropdown-toggle{background-color:#e60033}.btn-group.open .btn-success.dropdown-toggle{background-color:#379f15}.btn-group.open .btn-info.dropdown-toggle{background-color:#8d46b0}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#000}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert{padding:8px 35px 8px 14px;margin-bottom:22px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#ff7518;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert,.alert h4{color:#fff}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:22px}.alert-success{color:#fff;background-color:#3fb618;border-color:transparent}.alert-success h4{color:#fff}.alert-danger,.alert-error{color:#fff;background-color:#ff0039;border-color:transparent}.alert-danger h4,.alert-error h4{color:#fff}.alert-info{color:#fff;background-color:#9954bb;border-color:transparent}.alert-info h4{color:#fff}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:22px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:22px;color:#dfdfdf;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#007fff}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:10px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:22px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#bbb;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#007fff}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#007fff;border-bottom-color:#007fff}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#06c;border-bottom-color:#06c}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#bbb;border-bottom-color:#bbb}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#dfdfdf;border-color:#dfdfdf}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#dfdfdf}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#dfdfdf}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:22px;overflow:visible}.navbar-inner{min-height:50px;padding-right:20px;padding-left:20px;background-color:#080808;background-image:-moz-linear-gradient(top,#080808,#080808);background-image:-webkit-gradient(linear,0 0,0 100%,from(#080808),to(#080808));background-image:-webkit-linear-gradient(top,#080808,#080808);background-image:-o-linear-gradient(top,#080808,#080808);background-image:linear-gradient(to bottom,#080808,#080808);background-repeat:repeat-x;border:1px solid transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808',endColorstr='#ff080808',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:14px 20px 14px;margin-left:-20px;font-size:20px;font-weight:200;color:#fff;text-shadow:0 1px 0 #080808}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:50px;color:#fff}.navbar-link{color:#fff}.navbar-link:hover,.navbar-link:focus{color:#fff}.navbar .divider-vertical{height:50px;margin:0 9px;border-right:1px solid #080808;border-left:1px solid #080808}.navbar .btn,.navbar .btn-group{margin-top:10px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:10px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:10px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:14px 15px 14px;color:#fff;text-decoration:none;text-shadow:0 1px 0 #080808}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#fff;text-decoration:none;background-color:#3b3b3b}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#fff;text-decoration:none;background-color:transparent;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#000;*background-color:#000;background-image:-moz-linear-gradient(top,#000,#000);background-image:-webkit-gradient(linear,0 0,0 100%,from(#000),to(#000));background-image:-webkit-linear-gradient(top,#000,#000);background-image:-o-linear-gradient(top,#000,#000);background-image:linear-gradient(to bottom,#000,#000);background-repeat:repeat-x;border-color:#000 #000 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff000000',endColorstr='#ff000000',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#000;*background-color:#000}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#000 \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:transparent}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#007fff;background-image:-moz-linear-gradient(top,#007fff,#007fff);background-image:-webkit-gradient(linear,0 0,0 100%,from(#007fff),to(#007fff));background-image:-webkit-linear-gradient(top,#007fff,#007fff);background-image:-o-linear-gradient(top,#007fff,#007fff);background-image:linear-gradient(to bottom,#007fff,#007fff);background-repeat:repeat-x;border-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff007fff',endColorstr='#ff007fff',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#fff}.navbar-inverse .navbar-text{color:#fff}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:rgba(0,0,0,0.05)}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#007fff}.navbar-inverse .navbar-link{color:#fff}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#007fff;border-left-color:#007fff}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#007fff}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#80bfff;border-color:#007fff;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#999}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#999}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#999}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#999;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0072e6;*background-color:#0072e6;background-image:-moz-linear-gradient(top,#0072e6,#0072e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#0072e6),to(#0072e6));background-image:-webkit-linear-gradient(top,#0072e6,#0072e6);background-image:-o-linear-gradient(top,#0072e6,#0072e6);background-image:linear-gradient(to bottom,#0072e6,#0072e6);background-repeat:repeat-x;border-color:#0072e6 #0072e6 #004c99;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0072e6',endColorstr='#ff0072e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#0072e6;*background-color:#06c}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#0059b3 \9}.breadcrumb{padding:8px 15px;margin:0 0 22px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#dfdfdf}.pagination{margin:22px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:22px;text-decoration:none;background-color:#dfdfdf;border:1px solid transparent;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#007fff}.pagination ul>.active>a,.pagination ul>.active>span{color:#dfdfdf;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#dfdfdf;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:22px 30px;font-size:20px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-top-left-radius:0;border-top-left-radius:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topleft:0}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:0;border-top-right-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:13.6px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:2px 6px;font-size:12px}.pager{margin:22px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#dfdfdf;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#ff7518;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#ff7518;border-bottom:1px solid #fe6600;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:16px}.popover .arrow:after{border-width:15px;content:""}.popover.top .arrow{bottom:-16px;left:50%;margin-left:-16px;border-top-color:#999;border-top-color:transparent;border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-15px;border-top-color:#ff7518;border-bottom-width:0}.popover.right .arrow{top:50%;left:-16px;margin-top:-16px;border-right-color:#999;border-right-color:transparent;border-left-width:0}.popover.right .arrow:after{bottom:-15px;left:1px;border-right-color:#ff7518;border-left-width:0}.popover.bottom .arrow{top:-16px;left:50%;margin-left:-16px;border-bottom-color:#999;border-bottom-color:transparent;border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-15px;border-bottom-color:#ff7518;border-top-width:0}.popover.left .arrow{top:50%;right:-16px;margin-top:-16px;border-left-color:#999;border-left-color:transparent;border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-15px;border-left-color:#ff7518;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:22px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:22px;border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#007fff;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#bbb}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:13.536px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#dfdfdf}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#fff}.label-important[href],.badge-important[href]{background-color:#e6e6e6}.label-warning,.badge-warning{background-color:#ff7518}.label-warning[href],.badge-warning[href]{background-color:#e45c00}.label-success,.badge-success{background-color:#fff}.label-success[href],.badge-success[href]{background-color:#e6e6e6}.label-info,.badge-info{background-color:#fff}.label-info[href],.badge-info[href]{background-color:#e6e6e6}.label-inverse,.badge-inverse{background-color:#999}.label-inverse[href],.badge-inverse[href]{background-color:#808080}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:22px;margin-bottom:22px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#ff9046;background-image:-moz-linear-gradient(top,#ffa365,#ff7518);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ffa365),to(#ff7518));background-image:-webkit-linear-gradient(top,#ffa365,#ff7518);background-image:-o-linear-gradient(top,#ffa365,#ff7518);background-image:linear-gradient(to bottom,#ffa365,#ff7518);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffa365',endColorstr='#ffff7518',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#ffa365;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:22px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:22px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#080808;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#999;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:22px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:33px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:33px}body{overflow-y:scroll;font-weight:300}h1{font-size:50px}h2,h3{font-size:26px}h4{font-size:14px}h5,h6{font-size:11px}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#999}blockquote{padding:10px 15px;background-color:#eee;border-left-color:#bbb}blockquote.pull-right{padding:10px 15px;border-right-color:#bbb}blockquote small{color:#999}.muted{color:#bbb}.text-warning{color:#ff7518}a.text-warning:hover{color:#e45c00}.text-error{color:#ff0039}a.text-error:hover{color:#cc002e}.text-info{color:#9954bb}a.text-info:hover{color:#7e3f9d}.text-success{color:#3fb618}a.text-success:hover{color:#2f8912}.navbar .navbar-inner{background-image:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar .brand:hover{color:#fff}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{background-color:#3b3b3b;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#fff}.navbar .nav li.dropdown.open>.dropdown-toggle:hover,.navbar .nav li.dropdown.active>.dropdown-toggle:hover,.navbar .nav li.dropdown.open.active>.dropdown-toggle:hover{color:#eee}.navbar .navbar-search .search-query{line-height:normal}.navbar-inverse .brand,.navbar-inverse .nav>li>a{text-shadow:none}.navbar-inverse .brand:hover,.navbar-inverse .nav>.active>a,.navbar-inverse .nav>.active>a:hover,.navbar-inverse .nav>.active>a:focus{color:#fff;background-color:rgba(0,0,0,0.05);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.navbar-inverse .navbar-search .search-query{color:#080808}div.subnav{margin:0 1px;background:#dfdfdf none;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}div.subnav .nav{background-color:transparent}div.subnav .nav>li>a{border-color:transparent}div.subnav .nav>.active>a,div.subnav .nav>.active>a:hover{color:#fff;background-color:#000;border-color:transparent;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}div.subnav-fixed{top:51px;margin:0}.nav .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#007fff}.nav-tabs>li>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li>a:hover{color:#fff;background-color:#007fff}.nav-tabs.nav-stacked>.active>a,.nav-tabs.nav-stacked>.active>a:hover{color:#bbb;background-color:#fff}.nav-tabs.nav-stacked>li:first-child>a,.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.tabs-below>.nav-tabs>li>a,.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-pills>li>a{color:#000;background-color:#dfdfdf;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-pills>li>a:hover{color:#fff;background-color:#000}.nav-pills>.disabled>a,.nav-pills>.disabled>a:hover{color:#999;background-color:#eee}.nav-list>li>a{color:#080808}.nav-list>li>a:hover{color:#fff;text-shadow:none;background-color:#007fff}.nav-list .nav-header{font-size:16px;color:#000}.nav-list .divider{background-color:#bbb;border-bottom:0}.pagination ul{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.pagination ul>li>a,.pagination ul>li>span{margin-right:6px;color:#080808}.pagination ul>li>a:hover,.pagination ul>li>span:hover{color:#fff;background-color:#080808}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{margin-right:0}.pagination ul>.active>a,.pagination ul>.active>span{color:#fff}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;background-color:#eee}.pager li>a,.pager li>span{color:#080808;background-color:#dfdfdf;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.pager li>a:hover,.pager li>span:hover{color:#fff;background-color:#080808}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;background-color:#eee}.breadcrumb{background-color:#dfdfdf}.breadcrumb li{text-shadow:none}.breadcrumb .divider,.breadcrumb .active{color:#080808;text-shadow:none}.btn{padding:5px 12px;color:#080808;text-shadow:none;background-image:none;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn.disabled{box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus{color:#000}.btn-large{padding:22px 30px}.btn-small{padding:2px 10px}.btn-mini{padding:2px 6px}.btn-group>.btn:first-child,.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.dropdown-toggle{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.table tbody tr.success td{color:#fff}.table tbody tr.error td{color:#fff}.table tbody tr.info td{color:#fff}.table-bordered{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child,.table-bordered tfoot:last-child tr:last-child td:first-child{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"]{color:#080808}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#ff7518}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#080808;border-color:#ff7518}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#ff0039}.control-group.error input,.control-group.error select,.control-group.error textarea{color:#080808;border-color:#ff0039}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#3fb618}.control-group.success input,.control-group.success select,.control-group.success textarea{color:#080808;border-color:#3fb618}legend{color:#080808;border-bottom:0}.form-actions{background-color:#eee;border-top:0}.dropdown-menu{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert{text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.alert-heading,.alert h1,.alert h2,.alert h3,.alert h4,.alert h5,.alert h6{color:#fff}.label{min-width:80px;min-height:80px;font-weight:300;text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.label-success{background-color:#3fb618}.label-important{background-color:#ff0039}.label-info{background-color:#9954bb}.label-inverse{background-color:#000}.badge{font-weight:300;text-shadow:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.badge-success{background-color:#3fb618}.badge-important{background-color:#ff0039}.badge-info{background-color:#9954bb}.badge-inverse{background-color:#000}.hero-unit{border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.well{border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}[class^="icon-"],[class*=" icon-"]{margin:0 2px;vertical-align:-2px}a.thumbnail{background-color:#dfdfdf}a.thumbnail:hover{background-color:#bbb;border-color:transparent}.progress{height:6px;background-color:#eee;background-image:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.progress .bar{background-color:#007fff;background-image:none}.progress-info{background-color:#9954bb}.progress-success{background-color:#3fb618}.progress-warning{background-color:#ff7518}.progress-danger{background-color:#ff0039}.modal{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.modal-header{border-bottom:0}.modal-footer{background-color:transparent;border-top:0}.popover{color:#fff;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.popover-title{color:#fff;border-bottom:0}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}pre{margin-top:10px;color:#333}pre.terminal{font-size:1em;color:#c0c0c0;background:#000}.tlist li{padding-top:.3em;paddint-bottom:.3em} diff --git a/pathod/libpathod/static/bootstrap.min.js b/pathod/libpathod/static/bootstrap.min.js new file mode 100644 index 00000000..14356981 --- /dev/null +++ b/pathod/libpathod/static/bootstrap.min.js @@ -0,0 +1,6 @@ +/*! +* Bootstrap.js by @fat & @mdo +* Copyright 2012 Twitter, Inc. +* http://www.apache.org/licenses/LICENSE-2.0.txt +*/ +!function(a){a(function(){"use strict",a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.parent('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=c,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(b){return b||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},to:function(b){var c=this.$element.find(".active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(a){return a||(this.paused=!0),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this,j=a.Event("slide");this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();if(e.hasClass("active"))return;if(a.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(j);if(j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})}else{this.$element.trigger(j);if(j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=a.extend({},a.fn.carousel.defaults,typeof c=="object"&&c);e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):typeof c=="string"||(c=f.slide)?e[c]():f.interval&&e.cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b,c,d,e;if(this.transitioning)return;b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find("> .accordion-group > .in");if(d&&d.length){e=d.data("collapse");if(e&&e.transitioning)return;d.collapse("hide"),e||d.data("collapse",null)}this.$element[b](0),this.transition("addClass",a.Event("show"),"shown"),this.$element[b](this.$element[0][c])},hide:function(){var b;if(this.transitioning)return;b=this.dimension(),this.reset(this.$element[b]()),this.transition("removeClass",a.Event("hide"),"hidden"),this.$element[b](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c.type=="show"&&e.reset(),e.transitioning=0,e.$element.trigger(d)};this.$element.trigger(c);if(c.isDefaultPrevented())return;this.transitioning=1,this.$element[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();a(e).collapse(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}"use strict";var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e,f,g;if(c.is(".disabled, :disabled"))return;return f=c.attr("data-target"),f||(f=c.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,"")),e=a(f),e.length||(e=c.parent()),g=e.hasClass("open"),d(),g||e.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown",".dropdown form",function(a){a.stopPropagation()}).on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('<div class="modal-backdrop '+d+'" />').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,a.proxy(f,this)):f.call(this)):b&&b()}function f(){this.$backdrop.remove(),this.$backdrop=null}function g(){var b=this;this.isShown&&this.options.keyboard?a(document).on("keyup.dismiss.modal",function(a){a.which==27&&b.hide()}):this.isShown||a(document).off("keyup.dismiss.modal")}"use strict";var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this))};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this,c=a.Event("show");this.$element.trigger(c);if(this.isShown||c.isDefaultPrevented())return;a("body").addClass("modal-open"),this.isShown=!0,g.call(this),e.call(this,function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in"),c?b.$element.one(a.support.transition.end,function(){b.$element.trigger("shown")}):b.$element.trigger("shown")})},hide:function(b){b&&b.preventDefault();var e=this;b=a.Event("hide"),this.$element.trigger(b);if(!this.isShown||b.isDefaultPrevented())return;this.isShown=!1,a("body").removeClass("modal-open"),g.call(this),this.$element.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?c.call(this):d.call(this)}},a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a(function(){a("body").on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({},e.data(),c.data());b.preventDefault(),e.modal(f)})})}(window.jQuery),!function(a){"use strict";var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,this.options.trigger!="manual"&&(e=this.options.trigger=="hover"?"mouseenter":"focus",f=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(e,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f,this.options.selector,a.proxy(this.leave,this))),this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,b,this.$element.data()),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);if(!c.options.delay||!c.options.delay.show)return c.show();clearTimeout(this.timeout),c.hoverState="in",this.timeout=setTimeout(function(){c.hoverState=="in"&&c.show()},c.options.delay.show)},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!c.options.delay||!c.options.delay.hide)return c.hide();c.hoverState="out",this.timeout=setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide)},show:function(){var a,b,c,d,e,f,g;if(this.hasContent()&&this.enabled){a=this.tip(),this.setContent(),this.options.animation&&a.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,a[0],this.$element[0]):this.options.placement,b=/in/.test(f),a.remove().css({top:0,left:0,display:"block"}).appendTo(b?this.$element:document.body),c=this.getPosition(b),d=a[0].offsetWidth,e=a[0].offsetHeight;switch(b?f.split(" ")[1]:f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}a.css(g).addClass(f).addClass("in")}},isHTML:function(a){return typeof a!="string"||a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3||/^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(a)},setContent:function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.isHTML(b)?"html":"text"](b),a.removeClass("fade in top bottom left right")},hide:function(){function d(){var b=setTimeout(function(){c.off(a.support.transition.end).remove()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.remove()})}var b=this,c=this.tip();c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d():c.remove()},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(b){return a.extend({},b?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()}},a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0}}(window.jQuery),!function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.isHTML(b)?"html":"text"](b),a.find(".popover-content > *")[this.isHTML(c)?"html":"text"](c),a.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-content")||(typeof c.content=="function"?c.content.call(b[0]):c.content),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip}}),a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'})}(window.jQuery),!function(a){function b(b,c){var d=a.proxy(this.process,this),e=a(b).is("body")?a(window):a(b),f;this.options=a.extend({},a.fn.scrollspy.defaults,c),this.$scrollElement=e.on("scroll.scroll.data-api",d),this.selector=(this.options.target||(f=a(b).attr("href"))&&f.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=a("body"),this.refresh(),this.process()}"use strict",b.prototype={constructor:b,refresh:function(){var b=this,c;this.offsets=a([]),this.targets=a([]),c=this.$body.find(this.selector).map(function(){var b=a(this),c=b.data("target")||b.attr("href"),d=/^#\w/.test(c)&&a(c);return d&&c.length&&[[d.position().top,c]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},process:function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,c=b-this.$scrollElement.height(),d=this.offsets,e=this.targets,f=this.activeTarget,g;if(a>=c)return f!=(g=e.last()[0])&&this.activate(g);for(g=d.length;g--;)f!=e[g]&&a>=d[g]&&(!d[g+1]||a<=d[g+1])&&this.activate(e[g])},activate:function(b){var c,d;this.activeTarget=b,a(this.selector).parent(".active").removeClass("active"),d=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',c=a(d).parent("li").addClass("active"),c.parent(".dropdown-menu")&&(c=c.closest("li.dropdown").addClass("active")),c.trigger("activate")}},a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("scrollspy"),f=typeof c=="object"&&c;e||d.data("scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.defaults={offset:10},a(function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),!function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f,g;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active a").last()[0],g=a.Event("show",{relatedTarget:e}),b.trigger(g);if(g.isDefaultPrevented())return;f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}},a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a(function(){a("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.typeahead.defaults,c),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=a(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};b.prototype={constructor:b,select:function(){var a=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(a)).change(),this.hide()},updater:function(a){return a},show:function(){var b=a.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:b.top+b.height,left:b.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(b){var c=this,d,e;return this.query=this.$element.val(),this.query?(d=a.grep(this.source,function(a){return c.matcher(a)}),d=this.sorter(d),d.length?this.render(d.slice(0,this.options.items)).show():this.shown?this.hide():this):this.shown?this.hide():this},matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(a){var b=[],c=[],d=[],e;while(e=a.shift())e.toLowerCase().indexOf(this.query.toLowerCase())?~e.indexOf(this.query)?c.push(e):d.push(e):b.push(e);return b.concat(c,d)},highlighter:function(a){var b=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return a.replace(new RegExp("("+b+")","ig"),function(a,b){return"<strong>"+b+"</strong>"})},render:function(b){var c=this;return b=a(b).map(function(b,d){return b=a(c.options.item).attr("data-value",d),b.find("a").html(c.highlighter(d)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},next:function(b){var c=this.$menu.find(".active").removeClass("active"),d=c.next();d.length||(d=a(this.$menu.find("li")[0])),d.addClass("active")},prev:function(a){var b=this.$menu.find(".active").removeClass("active"),c=b.prev();c.length||(c=this.$menu.find("li").last()),c.addClass("active")},listen:function(){this.$element.on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),(a.browser.webkit||a.browser.msie)&&this.$element.on("keydown",a.proxy(this.keypress,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this))},keyup:function(a){switch(a.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}a.stopPropagation(),a.preventDefault()},keypress:function(a){if(!this.shown)return;switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:if(a.type!="keydown")break;a.preventDefault(),this.prev();break;case 40:if(a.type!="keydown")break;a.preventDefault(),this.next()}a.stopPropagation()},blur:function(a){var b=this;setTimeout(function(){b.hide()},150)},click:function(a){a.stopPropagation(),a.preventDefault(),this.select()},mouseenter:function(b){this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")}},a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f=typeof c=="object"&&c;e||d.data("typeahead",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>'},a.fn.typeahead.Constructor=b,a(function(){a("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;b.preventDefault(),c.typeahead(c.data())})})}(window.jQuery);
\ No newline at end of file diff --git a/pathod/libpathod/static/jquery-1.7.2.min.js b/pathod/libpathod/static/jquery-1.7.2.min.js new file mode 100644 index 00000000..16ad06c5 --- /dev/null +++ b/pathod/libpathod/static/jquery-1.7.2.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.2 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"<!doctype html>":"")+"<html><body>"),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):b_(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&f.type(b)==="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function bZ(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bZ(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bZ(a,c,d,e,"*",g));return l}function bY(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?1:0,g=4;if(d>0){if(c!=="border")for(;e<g;e+=2)c||(d-=parseFloat(f.css(a,"padding"+bx[e]))||0),c==="margin"?d+=parseFloat(f.css(a,c+bx[e]))||0:d-=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0;return d+"px"}d=by(a,b);if(d<0||d==null)d=a.style[b];if(bt.test(d))return d;d=parseFloat(d)||0;if(c)for(;e<g;e+=2)d+=parseFloat(f.css(a,"padding"+bx[e]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+bx[e]))||0);return d+"px"}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;b.nodeType===1&&(b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?b.outerHTML=a.outerHTML:c!=="input"||a.type!=="checkbox"&&a.type!=="radio"?c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text):(a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value)),b.removeAttribute(f.expando),b.removeAttribute("_submit_attached"),b.removeAttribute("_change_attached"))}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c,i[c][d])}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h,i){var j,k=d==null,l=0,m=a.length;if(d&&typeof d=="object"){for(l in d)e.access(a,c,l,d[l],1,h,f);g=1}else if(f!==b){j=i===b&&e.isFunction(f),k&&(j?(j=c,c=function(a,b,c){return j.call(e(a),c)}):(c.call(a,f),c=null));if(c)for(;l<m;l++)c(a[l],d,j?f.call(a[l],l,c(a[l],d)):f,i);g=1}return g?a:k?c.call(a):m?c(a[0],d):h},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test("Â ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m,n=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?n(g):h==="function"&&(!a.unique||!p.has(g))&&c.push(g)},o=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,j=!0,m=k||0,k=0,l=c.length;for(;c&&m<l;m++)if(c[m].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}j=!1,c&&(a.once?e===!0?p.disable():c=[]:d&&d.length&&(e=d.shift(),p.fireWith(e[0],e[1])))},p={add:function(){if(c){var a=c.length;n(arguments),j?l=c.length:e&&e!==!0&&(k=a,o(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){j&&f<=l&&(l--,f<=m&&m--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&p.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(j?a.once||d.push([b,c]):(!a.once||!e)&&o(b,c));return this},fire:function(){p.fireWith(this,arguments);return this},fired:function(){return!!i}};return p};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p=c.createElement("div"),q=c.documentElement;p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="<div "+n+"display:block;'><div style='"+t+"0;display:block;overflow:hidden;'></div></div>"+"<table "+n+"' cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="<table><tr><td style='"+t+"0;display:none'></td><td>t</td></tr></table>",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="<div style='width:5px;'></div>",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h,i,j=this[0],k=0,m=null;if(a===b){if(this.length){m=f.data(j);if(j.nodeType===1&&!f._data(j,"parsedAttrs")){g=j.attributes;for(i=g.length;k<i;k++)h=g[k].name,h.indexOf("data-")===0&&(h=f.camelCase(h.substring(5)),l(j,h,m[h]));f._data(j,"parsedAttrs",!0)}}return m}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!";return f.access(this,function(c){if(c===b){m=this.triggerHandler("getData"+e,[d[0]]),m===b&&j&&(m=f.data(j,a),m=l(j,a,m));return m===b&&d[1]?this.data(d[0]):m}d[1]=c,this.each(function(){var b=f(this);b.triggerHandler("setData"+e,d),f.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length<d)return f.queue(this[0],a);return c===b?this:this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise(c)}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,f.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i<g;i++)e=d[i],e&&(c=f.propFix[e]||e,h=u.test(e),h||f.attr(a,e,""),a.removeAttribute(v?e:c),h&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0,coords:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:g&&G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=f.event.special[c.type]||{},j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(!i.preDispatch||i.preDispatch.call(this,c)!==!1){if(e&&(!c.button||c.type!=="click")){n=f(this),n.context=this.ownerDocument||this;for(m=c.target;m!=this;m=m.parentNode||this)if(m.disabled!==!0){p={},r=[],n[0]=m;for(k=0;k<e;k++)s=d[k],t=s.selector,p[t]===b&&(p[t]=s.quick?H(m,s.quick):n.is(t)),p[t]&&r.push(s);r.length&&j.push({elem:m,matches:r})}}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){q=j[k],c.currentTarget=q.elem;for(l=0;l<q.matches.length&&!c.isImmediatePropagationStopped();l++){s=q.matches[l];if(h||!c.namespace&&!s.namespace||c.namespace_re&&c.namespace_re.test(s.namespace))c.data=s.data,c.handleObj=s,o=((f.event.special[s.origType]||{}).handle||s.handler).apply(q.elem,g),o!==b&&(c.result=o,o===!1&&(c.preventDefault(),c.stopPropagation()))}}i.postDispatch&&i.postDispatch.call(this,c);return c.result}},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),d._submit_attached=!0)})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9||d===11){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.globalPOS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")[\\s/>]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(f.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(g){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,function(a,b){b.src?f.ajax({type:"GET",global:!1,url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1></$2>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]==="<table>"&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i<u;i++)bn(l[i]);else bn(l);l.nodeType?j.push(l):j=f.merge(j,l)}if(d){g=function(a){return!a.type||be.test(a.type)};for(k=0;j[k];k++){h=j[k];if(e&&f.nodeName(h,"script")&&(!h.type||be.test(h.type)))e.push(h.parentNode?h.parentNode.removeChild(h):h);else{if(h.nodeType===1){var v=f.grep(h.getElementsByTagName("script"),g);j.splice.apply(j,[k+1,0].concat(v))}d.appendChild(h)}}}return j},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bp=/alpha\([^)]*\)/i,bq=/opacity=([^)]*)/,br=/([A-Z]|^ms)/g,bs=/^[\-+]?(?:\d*\.)?\d+$/i,bt=/^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,bu=/^([\-+])=([\-+.\de]+)/,bv=/^margin/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Top","Right","Bottom","Left"],by,bz,bA;f.fn.css=function(a,c){return f.access(this,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)},a,c,arguments.length>1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),(e===""&&f.css(d,"display")==="none"||!f.contains(d.ownerDocument.documentElement,d))&&f._data(d,"olddisplay",cu(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(ct("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(ct("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o,p,q;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]);if((k=f.cssHooks[g])&&"expand"in k){l=k.expand(a[g]),delete a[g];for(i in l)i in a||(a[i]=l[i])}}for(g in a){h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cu(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cm.test(h)?(q=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),q?(f._data(this,"toggle"+i,q==="show"?"hide":"show"),j[q]()):j[h]()):(m=cn.exec(h),n=j.cur(),m?(o=parseFloat(m[2]),p=m[3]||(f.cssNumber[i]?"":"px"),p!=="px"&&(f.style(this,i,(o||1)+p),n=(o||1)/j.cur()*n,f.style(this,i,n+p)),m[1]&&(o=(m[1]==="-="?-1:1)*o+n),j.custom(n,o,p)):j.custom(n,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:ct("show",1),slideUp:ct("hide",1),slideToggle:ct("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a){return a},swing:function(a){return-Math.cos(a*Math.PI)/2+.5}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cq||cr(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){f._data(e.elem,"fxshow"+e.prop)===b&&(e.options.hide?f._data(e.elem,"fxshow"+e.prop,e.start):e.options.show&&f._data(e.elem,"fxshow"+e.prop,e.end))},h()&&f.timers.push(h)&&!co&&(co=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cq||cr(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(cp.concat.apply([],cp),function(a,b){b.indexOf("margin")&&(f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)})}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cv,cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?cv=function(a,b,c,d){try{d=a.getBoundingClientRect()}catch(e){}if(!d||!f.contains(c,a))return d?{top:d.top,left:d.left}:{top:0,left:0};var g=b.body,h=cy(b),i=c.clientTop||g.clientTop||0,j=c.clientLeft||g.clientLeft||0,k=h.pageYOffset||f.support.boxModel&&c.scrollTop||g.scrollTop,l=h.pageXOffset||f.support.boxModel&&c.scrollLeft||g.scrollLeft,m=d.top+k-i,n=d.left+l-j;return{top:m,left:n}}:cv=function(a,b,c){var d,e=a.offsetParent,g=a,h=b.body,i=b.defaultView,j=i?i.getComputedStyle(a,null):a.currentStyle,k=a.offsetTop,l=a.offsetLeft;while((a=a.parentNode)&&a!==h&&a!==c){if(f.support.fixedPosition&&j.position==="fixed")break;d=i?i.getComputedStyle(a,null):a.currentStyle,k-=a.scrollTop,l-=a.scrollLeft,a===e&&(k+=a.offsetTop,l+=a.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(a.nodeName))&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),g=e,e=a.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),j=d}if(j.position==="relative"||j.position==="static")k+=h.offsetTop,l+=h.offsetLeft;f.support.fixedPosition&&j.position==="fixed"&&(k+=Math.max(c.scrollTop,h.scrollTop),l+=Math.max(c.scrollLeft,h.scrollLeft));return{top:k,left:l}},f.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){f.offset.setOffset(this,a,b)});var c=this[0],d=c&&c.ownerDocument;if(!d)return null;if(c===d.body)return f.offset.bodyOffset(c);return cv(c,d,d.documentElement)},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file diff --git a/pathod/libpathod/static/jquery.localscroll-min.js b/pathod/libpathod/static/jquery.localscroll-min.js new file mode 100644 index 00000000..3f8d64cc --- /dev/null +++ b/pathod/libpathod/static/jquery.localscroll-min.js @@ -0,0 +1,9 @@ +/**
+ * jQuery.LocalScroll - Animated scrolling navigation, using anchors.
+ * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
+ * Dual licensed under MIT and GPL.
+ * Date: 3/11/2009
+ * @author Ariel Flesler
+ * @version 1.2.7
+ **/
+;(function($){var l=location.href.replace(/#.*/,'');var g=$.localScroll=function(a){$('body').localScroll(a)};g.defaults={duration:1e3,axis:'y',event:'click',stop:true,target:window,reset:true};g.hash=function(a){if(location.hash){a=$.extend({},g.defaults,a);a.hash=false;if(a.reset){var e=a.duration;delete a.duration;$(a.target).scrollTo(0,a);a.duration=e}i(0,location,a)}};$.fn.localScroll=function(b){b=$.extend({},g.defaults,b);return b.lazy?this.bind(b.event,function(a){var e=$([a.target,a.target.parentNode]).filter(d)[0];if(e)i(a,e,b)}):this.find('a,area').filter(d).bind(b.event,function(a){i(a,this,b)}).end().end();function d(){return!!this.href&&!!this.hash&&this.href.replace(this.hash,'')==l&&(!b.filter||$(this).is(b.filter))}};function i(a,e,b){var d=e.hash.slice(1),f=document.getElementById(d)||document.getElementsByName(d)[0];if(!f)return;if(a)a.preventDefault();var h=$(b.target);if(b.lock&&h.is(':animated')||b.onBefore&&b.onBefore.call(b,a,f,h)===false)return;if(b.stop)h.stop(true);if(b.hash){var j=f.id==d?'id':'name',k=$('<a> </a>').attr(j,d).css({position:'absolute',top:$(window).scrollTop(),left:$(window).scrollLeft()});f[j]='';$('body').prepend(k);location=e.hash;k.remove();f[j]=d}h.scrollTo(f,b).trigger('notify.serialScroll',[f])}})(jQuery);
\ No newline at end of file diff --git a/pathod/libpathod/static/jquery.scrollTo-min.js b/pathod/libpathod/static/jquery.scrollTo-min.js new file mode 100644 index 00000000..7d4001dc --- /dev/null +++ b/pathod/libpathod/static/jquery.scrollTo-min.js @@ -0,0 +1,11 @@ +/**
+ * jQuery.ScrollTo - Easy element scrolling using jQuery.
+ * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
+ * Dual licensed under MIT and GPL.
+ * Date: 3/9/2009
+ * @author Ariel Flesler
+ * @version 1.4.1
+ *
+ * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
+ */
+;(function($){var m=$.scrollTo=function(b,h,f){$(window).scrollTo(b,h,f)};m.defaults={axis:'xy',duration:parseFloat($.fn.jquery)>=1.3?0:1};m.window=function(b){return $(window).scrollable()};$.fn.scrollable=function(){return this.map(function(){var b=this,h=!b.nodeName||$.inArray(b.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!h)return b;var f=(b.contentWindow||b).document||b.ownerDocument||b;return $.browser.safari||f.compatMode=='BackCompat'?f.body:f.documentElement})};$.fn.scrollTo=function(l,j,a){if(typeof j=='object'){a=j;j=0}if(typeof a=='function')a={onAfter:a};if(l=='max')l=9e9;a=$.extend({},m.defaults,a);j=j||a.speed||a.duration;a.queue=a.queue&&a.axis.length>1;if(a.queue)j/=2;a.offset=n(a.offset);a.over=n(a.over);return this.scrollable().each(function(){var k=this,o=$(k),d=l,p,g={},q=o.is('html,body');switch(typeof d){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px)?$/.test(d)){d=n(d);break}d=$(d,this);case'object':if(d.is||d.style)p=(d=$(d)).offset()}$.each(a.axis.split(''),function(b,h){var f=h=='x'?'Left':'Top',i=f.toLowerCase(),c='scroll'+f,r=k[c],s=h=='x'?'Width':'Height';if(p){g[c]=p[i]+(q?0:r-o.offset()[i]);if(a.margin){g[c]-=parseInt(d.css('margin'+f))||0;g[c]-=parseInt(d.css('border'+f+'Width'))||0}g[c]+=a.offset[i]||0;if(a.over[i])g[c]+=d[s.toLowerCase()]()*a.over[i]}else g[c]=d[i];if(/^\d+$/.test(g[c]))g[c]=g[c]<=0?0:Math.min(g[c],u(s));if(!b&&a.queue){if(r!=g[c])t(a.onAfterFirst);delete g[c]}});t(a.onAfter);function t(b){o.animate(g,j,a.easing,b&&function(){b.call(this,l,a)})};function u(b){var h='scroll'+b;if(!q)return k[h];var f='client'+b,i=k.ownerDocument.documentElement,c=k.ownerDocument.body;return Math.max(i[h],c[h])-Math.min(i[f],c[f])}}).end()};function n(b){return typeof b=='object'?b:{top:b,left:b}}})(jQuery);
\ No newline at end of file diff --git a/pathod/libpathod/static/pathod.css b/pathod/libpathod/static/pathod.css new file mode 100644 index 00000000..8b23b4d5 --- /dev/null +++ b/pathod/libpathod/static/pathod.css @@ -0,0 +1,56 @@ + + +.fronttable { +} + +.bigtitle { + font-weight: bold; + font-size: 50px; + line-height: 55px; + text-align: center; + display: table; + height: 300px; +} + +.bigtitle>div { + display: table-cell; + vertical-align: middle; +} + +section { + margin-top: 50px; +} + +.example { + margin-top: 10px; + margin-bottom: 10px; +} + +.terminal { + margin-top: 10px; + margin-bottom: 10px; + background: #000; + color: #fff; +} + +.innerlink { + text-decoration: none; + border-bottom:1px dotted; + margin-bottom: 15px; +} + +.masthead { + padding: 50px 0 60px; + text-align: center; + +} + +.header { + font-size: 1.5em; +} + +.page-header { + margin-top: 10px; + + +} diff --git a/pathod/libpathod/static/start_quote.png b/pathod/libpathod/static/start_quote.png Binary files differnew file mode 100644 index 00000000..8090f6e8 --- /dev/null +++ b/pathod/libpathod/static/start_quote.png diff --git a/pathod/libpathod/static/syntax.css b/pathod/libpathod/static/syntax.css new file mode 100644 index 00000000..e371658a --- /dev/null +++ b/pathod/libpathod/static/syntax.css @@ -0,0 +1,120 @@ +.highlight { background: #f8f8f8; } +.highlight .c { color: #408080; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #BC7A00 } /* Comment.Preproc */ +.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #808080 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0040D0 } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #7D9029 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #A0A000 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #BB6688 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ +.grokdoc { background: #f8f8f8; } +.grokdoc .c { color: #408080; font-style: italic } /* Comment */ +.grokdoc .err { border: 1px solid #FF0000 } /* Error */ +.grokdoc .k { color: #008000; font-weight: bold } /* Keyword */ +.grokdoc .o { color: #666666 } /* Operator */ +.grokdoc .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +.grokdoc .cp { color: #BC7A00 } /* Comment.Preproc */ +.grokdoc .c1 { color: #408080; font-style: italic } /* Comment.Single */ +.grokdoc .cs { color: #408080; font-style: italic } /* Comment.Special */ +.grokdoc .gd { color: #A00000 } /* Generic.Deleted */ +.grokdoc .ge { font-style: italic } /* Generic.Emph */ +.grokdoc .gr { color: #FF0000 } /* Generic.Error */ +.grokdoc .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.grokdoc .gi { color: #00A000 } /* Generic.Inserted */ +.grokdoc .go { color: #808080 } /* Generic.Output */ +.grokdoc .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.grokdoc .gs { font-weight: bold } /* Generic.Strong */ +.grokdoc .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.grokdoc .gt { color: #0040D0 } /* Generic.Traceback */ +.grokdoc .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.grokdoc .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.grokdoc .kp { color: #008000 } /* Keyword.Pseudo */ +.grokdoc .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.grokdoc .kt { color: #B00040 } /* Keyword.Type */ +.grokdoc .m { color: #666666 } /* Literal.Number */ +.grokdoc .s { color: #BA2121 } /* Literal.String */ +.grokdoc .na { color: #7D9029 } /* Name.Attribute */ +.grokdoc .nb { color: #008000 } /* Name.Builtin */ +.grokdoc .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.grokdoc .no { color: #880000 } /* Name.Constant */ +.grokdoc .nd { color: #AA22FF } /* Name.Decorator */ +.grokdoc .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.grokdoc .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +.grokdoc .nf { color: #0000FF } /* Name.Function */ +.grokdoc .nl { color: #A0A000 } /* Name.Label */ +.grokdoc .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.grokdoc .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.grokdoc .nv { color: #19177C } /* Name.Variable */ +.grokdoc .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.grokdoc .w { color: #bbbbbb } /* Text.Whitespace */ +.grokdoc .mf { color: #666666 } /* Literal.Number.Float */ +.grokdoc .mh { color: #666666 } /* Literal.Number.Hex */ +.grokdoc .mi { color: #666666 } /* Literal.Number.Integer */ +.grokdoc .mo { color: #666666 } /* Literal.Number.Oct */ +.grokdoc .sb { color: #BA2121 } /* Literal.String.Backtick */ +.grokdoc .sc { color: #BA2121 } /* Literal.String.Char */ +.grokdoc .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.grokdoc .s2 { color: #BA2121 } /* Literal.String.Double */ +.grokdoc .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +.grokdoc .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.grokdoc .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +.grokdoc .sx { color: #008000 } /* Literal.String.Other */ +.grokdoc .sr { color: #BB6688 } /* Literal.String.Regex */ +.grokdoc .s1 { color: #BA2121 } /* Literal.String.Single */ +.grokdoc .ss { color: #19177C } /* Literal.String.Symbol */ +.grokdoc .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.grokdoc .vc { color: #19177C } /* Name.Variable.Class */ +.grokdoc .vg { color: #19177C } /* Name.Variable.Global */ +.grokdoc .vi { color: #19177C } /* Name.Variable.Instance */ +.grokdoc .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/pathod/libpathod/static/torture.png b/pathod/libpathod/static/torture.png Binary files differnew file mode 100644 index 00000000..50e245ea --- /dev/null +++ b/pathod/libpathod/static/torture.png diff --git a/pathod/libpathod/templates/about.html b/pathod/libpathod/templates/about.html new file mode 100644 index 00000000..340dc386 --- /dev/null +++ b/pathod/libpathod/templates/about.html @@ -0,0 +1,22 @@ +{% extends "frame.html" %} {% block body %} +<section> + <div class="page-header"> + <h1>About</h1> + </div> + <div class="row"> + <div class="span6"> + <div> + <p>pathod is developed by <a href="http://corte.si">Aldo Cortesi</a>.</p> + </div> + + <div> + <ul> + <li>email: <a href="mailto:aldo@corte.si">aldo@corte.si</a></li> + <li>twitter: <a href="http://twitter.com/cortesi">@cortesi</a></li> + <li>github: <a href="https://github.com/cortesi">github.com/cortesi</a></li> + </ul> + </div> + </div> + </div> +</section> +{% endblock %} diff --git a/pathod/libpathod/templates/docframe.html b/pathod/libpathod/templates/docframe.html new file mode 100644 index 00000000..4cbdeebc --- /dev/null +++ b/pathod/libpathod/templates/docframe.html @@ -0,0 +1,26 @@ +{% extends "layout.html" %} + +{% macro subs(s) %} + {% if subsection == s %} + class="active" + {% endif %} +{% endmacro %} + +{% block content %} +<div class="row"> + <div class="span3"> + <div class="well sidebar-nav"> + <ul class="nav nav-list"> + <li {{subs( "pathod")}}><a href="/docs/pathod">pathod</a></li> + <li {{subs( "pathoc")}}><a href="/docs/pathoc">pathoc</a></li> + <li {{subs( "lang")}}><a href="/docs/language">language</a></li> + <li {{subs( "libpathod")}}><a href="/docs/libpathod">libpathod</a></li> + <li {{subs( "test")}}><a href="/docs/test">libpathod.test</a></li> + </ul> + </div> + </div> + <div class="span9"> + {% block body %} {% endblock %} + </div> +</div> +{% endblock %} diff --git a/pathod/libpathod/templates/docs_lang.html b/pathod/libpathod/templates/docs_lang.html new file mode 100644 index 00000000..a1d22aef --- /dev/null +++ b/pathod/libpathod/templates/docs_lang.html @@ -0,0 +1,196 @@ +{% extends "docframe.html" %} {% block body %} +<div class="page-header"> + <h1> + Language Spec + <small>The mini-language at the heart of pathoc and pathod.</small> + </h1> +</div> + +<ul class="nav nav-tabs"> + <li class="active"><a href="#specifying_requests" data-toggle="tab">HTTP Requests</a></li> + <li><a href="#specifying_responses" data-toggle="tab">HTTP Responses</a></li> + <li><a href="#websockets" data-toggle="tab">Websocket Frames</a></li> +</ul> + +<div class="tab-content"> + <div class="tab-pane" id="specifying_responses"> + {% include "docs_lang_responses.html" %} + </div> + <div class="tab-pane active" id="specifying_requests"> + {% include "docs_lang_requests.html" %} + </div> + <div class="tab-pane" id="websockets"> + {% include "docs_lang_websockets.html" %} + </div> +</div> + +<section id="features"> + <div class="page-header"> + <h1>Features</h1> + </div> + + <a id="offsetspec"></a> + <h2>OFFSET</h2> + + <p> + Offsets are calculated relative to the base message, before any injections or other + transforms are applied. They have 3 flavors: + </p> + + <ul> + <li>An integer byte offset </li> + <li><b>r</b> for a random location</li> + <li><b>a</b> for the end of the message</li> + </ul> + + <a id="valuespec"></a> + <h2>VALUE</h2> + + <h3>Literals</h3> + + <p>Literal values are specified as a quoted strings: </p> + + <pre class="example">"foo"</pre> + + <p> + Either single or double quotes are accepted, and quotes can be escaped with backslashes + within the string: + </p> + + <pre class="example">'fo\'o'</pre> + + <p>Literal values can contain Python-style backslash escape sequences:</p> + + <pre class="example">'foo\r\nbar'</pre> + + <h3>Files</h3> + + <p> + You can load a value from a specified file path. To do so, you have to specify a + _staticdir_ option to pathod on the command-line, like so: + </p> + + <pre class="example">pathod -d ~/myassets</pre> + + <p> + All paths are relative paths under this directory. File loads are indicated by starting + the value specifier with the left angle bracket: + </p> + + <pre class="example"><my/path</pre> + + <p>The path value can also be a quoted string, with the same syntax as literals:</p> + + <pre class="example"><"my/path"</pre> + + + <h3>Generated values</h3> + + <p> + An @-symbol lead-in specifies that generated data should be used. There are two components + to a generator specification - a size, and a data type. By default pathod + assumes a data type of "bytes". + </p> + + <p>Here's a value specifier for generating 100 bytes: + + <pre class="example">@100</pre> + </p> + + <p> + You can use standard suffixes to indicate larger values. Here, for instance, is a + specifier for generating 100 megabytes: + </p> + + <pre class="example">@100m</pre> + + <p> + Data is generated and served efficiently - if you really want to send a terabyte + of data to a client, pathod can do it. The supported suffixes are: + </p> + + <table class="table table-bordered"> + <tbody> + <tr> + <td>b</td> + <td>1024**0 (bytes)</td> + </tr> + <tr> + <td>k</td> + <td>1024**1 (kilobytes)</td> + </tr> + <tr> + <td>m</td> + <td>1024**2 (megabytes)</td> + </tr> + <tr> + <td>g</td> + <td>1024**3 (gigabytes)</td> + </tr> + <tr> + <td>t</td> + <td>1024**4 (terabytes)</td> + </tr> + </tbody> + </table> + + <p> + Data types are separated from the size specification by a comma. This specification + generates 100mb of ASCII: + </p> + + <pre class="example">@100m,ascii</pre> + + <p>Supported data types are:</p> + + <table class="table table-bordered"> + <tbody> + <tr> + <td>ascii</td> + <td>All ASCII characters</td> + </tr> + <tr> + <td>ascii_letters</td> + <td>A-Za-z</td> + </tr> + <tr> + <td>ascii_lowercase</td> + <td>a-z</td> + </tr> + <tr> + <td>ascii_uppercase</td> + <td>A-Z</td> + </tr> + <tr> + <td>bytes</td> + <td>All 256 byte values</td> + </tr> + <tr> + <td>digits</td> + <td>0-9</td> + </tr> + <tr> + <td>hexdigits</td> + <td>0-f</td> + </tr> + <tr> + <td>octdigits</td> + <td>0-7</td> + </tr> + <tr> + <td>punctuation</td> + <td> + <pre>!"#$%&\'()*+,-./:; + <=>?@[\\]^_`{|}~</pre> + </td> + </tr> + <tr> + <td>whitespace</td> + <td> + <pre>\t\n\x0b\x0c\r and space</pre> + </td> + </tr> + </tbody> + </table> +</section> +{% endblock %} diff --git a/pathod/libpathod/templates/docs_lang_requests.html b/pathod/libpathod/templates/docs_lang_requests.html new file mode 100644 index 00000000..81aff535 --- /dev/null +++ b/pathod/libpathod/templates/docs_lang_requests.html @@ -0,0 +1,114 @@ +<pre class="example">method:path:[colon-separated list of features]</pre> +</p> + +<table class="table table-bordered"> + <tbody> + <tr> + <td>method</td> + <td> + <p> + A <a href="#valuespec">VALUE</a> specifying the HTTP method to + use. Standard methods do not need to be enclosed in quotes, while + non-standard methods can be specified as quoted strings. + </p> + + <p> + The special method <b>ws</b> creates a valid websocket upgrade + GET request, and signals to pathoc to switch to websocket recieve + mode if the server responds correctly. Apart from that, websocket + requests are just like any other, and all aspects of the request + can be over-ridden. + </p> + </td> + </tr> + + <tr> + <td>h<a href="#valuespec">VALUE</a>=<a href="#valuespec">VALUE</a></td> + <td> + Set a header. + </td> + </tr> + + <tr> + <td>r</td> + <td> + Set the "raw" flag on this response. Pathod will not calculate a Content-Length header + if a body is set. + </td> + </tr> + + <tr> + <td>c<a href="#valuespec">VALUE</a></td> + <td> + A shortcut for setting the Content-Type header. Equivalent to h"Content-Type"=VALUE + </td> + </tr> + + <tr> + <td>u<a href="#valuespec">VALUE</a> + <br> uSHORTCUT + </td> + + <td> + Set a User-Agent header on this request. You can specify either a complete + <a href="#valuespec">VALUE</a>, or a User-Agent shortcut: + + <table class="table table-condensed"> + {% for i in uastrings %} + <tr> + <td><b>{{ i[1] }}</b></td> + <td>{{ i[0] }}</td> + </tr> + {% endfor %} + </table> + </td> + </tr> + + <tr> + <td>b<a href="#valuespec">VALUE</a></td> + <td> + Set the body. The appropriate Content-Length header is added automatically unless + the "r" flag is set. + </td> + </tr> + + <tr> + <td>s<a href="#valuespec">VALUE</a></td> + <td> + An embedded Response specification, appended to the path of the request. + </td> + </tr> + + <tr> + <td>x<a href="#valuespec">INTEGER</a></td> + <td> + Repeat this message N times. + </td> + </tr> + + <tr> + <td>d<a href="#offsetspec">OFFSET</a></td> + <td> + <span class="badge badge-info">HTTP/1 only</span> Disconnect after + OFFSET bytes. + </td> + </tr> + + <tr> + <td>i<a href="#offsetspec">OFFSET</a>,<a href="#valuespec">VALUE</a></td> + <td> + <span class="badge badge-info">HTTP/1 only</span> Inject the specified + value at the offset. + </td> + </tr> + + <tr> + <td>p<a href="#offsetspec">OFFSET</a>,SECONDS</td> + <td> + <span class="badge badge-info">HTTP/1 only</span> Pause for SECONDS + seconds after OFFSET bytes. SECONDS can be an integer or "f" to pause + forever. + </td> + </tr> + </tbody> +</table> diff --git a/pathod/libpathod/templates/docs_lang_responses.html b/pathod/libpathod/templates/docs_lang_responses.html new file mode 100644 index 00000000..9a85ff1a --- /dev/null +++ b/pathod/libpathod/templates/docs_lang_responses.html @@ -0,0 +1,88 @@ +<pre class="example">code:[colon-separated list of features]</pre> + +<table class="table table-bordered"> + <tbody> + <tr> + <td>code</td> + <td> + <p>An integer specifying the HTTP response code.</p> + <p> + The special method <b>ws</b> creates a valid websocket upgrade + response (code 101), and moves pathod to websocket mode. Apart + from that, websocket responses are just like any other, and all + aspects of the response can be over-ridden. + </p> + </td> + </tr> + + <tr> + <td>m<a href="#valuespec">VALUE</a></td> + <td> + <span class="badge badge-info">HTTP/1 only</span> HTTP Reason message. + Automatically chosen according to the response code if not specified. + </td> + </tr> + + <tr> + <td>h<a href="#valuespec">VALUE</a>=<a href="#valuespec">VALUE</a></td> + <td> + Set a header. + </td> + </tr> + + <tr> + <td>r</td> + <td> + Set the "raw" flag on this response. Pathod will not calculate a Content-Length header + if a body is set, or add a Date header to the response. + </td> + </tr> + + <tr> + <td>l<a href="#valuespec">VALUE</a></td> + <td> + A shortcut for setting the Location header. Equivalent to h"Location"=VALUE + </td> + </tr> + + <tr> + <td>c<a href="#valuespec">VALUE</a></td> + <td> + A shortcut for setting the Content-Type header. Equivalent to h"Content-Type"=VALUE + </td> + </tr> + + <tr> + <td>b<a href="#valuespec">VALUE</a></td> + <td> + Set the body. The appropriate Content-Length header is added automatically unless + the "r" flag is set. + </td> + </tr> + + <tr> + <td>d<a href="#offsetspec">OFFSET</a></td> + <td> + <span class="badge badge-info">HTTP/1 only</span> Disconnect after + OFFSET bytes. + </td> + </tr> + + <tr> + <td>i<a href="#offsetspec">OFFSET</a>,<a href="#valuespec">VALUE</a></td> + <td> + <span class="badge badge-info">HTTP/1 only</span> Inject the specified + value at the offset. + </td> + </tr> + + <tr> + <td>p<a href="#offsetspec">OFFSET</a>,SECONDS</td> + <td> + <span class="badge badge-info">HTTP/1 only</span> Pause for SECONDS + seconds after OFFSET bytes. SECONDS can be an integer or "f" to pause + forever. + </td> + </tr> + </tbody> +</table> diff --git a/pathod/libpathod/templates/docs_lang_websockets.html b/pathod/libpathod/templates/docs_lang_websockets.html new file mode 100644 index 00000000..dd318e0b --- /dev/null +++ b/pathod/libpathod/templates/docs_lang_websockets.html @@ -0,0 +1,115 @@ +<pre class="example">wf:[colon-separated list of features]</pre> +</p> + +<table class="table table-bordered"> + <tbody> + + <tr> + <td> b<a href="#valuespec">VALUE</a> </td> + <td> + Set the frame payload. If a masking key is present, the value is encoded automatically. + </td> + </tr> + + <tr> + <td> c<a href="#valuespec">INTEGER</a> </td> + <td> + + Set the op code. This can either be an integer from 0-15, or be one of the following + opcode names: <b>text</b> (the default), + <b>continue</b>, <b>binary</b>, <b>close</b>, <b>ping</b>, + <b>pong</b>. + + </td> + </tr> + + <tr> + <td> d<a href="#offsetspec">OFFSET</a> </td> + <td> + Disconnect after OFFSET bytes. + </td> + </tr> + + <tr> + <td> [-]fin </td> + <td> + Set or un-set the <b>fin</b> bit. + </td> + </tr> + + <tr> + <td> i<a href="#offsetspec">OFFSET</a>,<a href="#valuespec">VALUE</a> </td> + <td> + Inject the specified value at the offset. + </td> + </tr> + + <tr> + <td> k<a href="#valuespec">VALUE</a> </td> + <td> + Set the masking key. The resulting value must be exactly 4 bytes long. The special + form + <b>knone</b> specifies that no key should be set, even if the mask + bit is on. + </td> + </tr> + + <tr> + <td> l<a href="#valuespec">INTEGER</a> </td> + <td> + Set the payload length in the frame header, regardless of the actual body length. + </td> + </tr> + + <tr> + <td> [-]mask </td> + <td> + Set or un-set the <b>mask</b> bit. + </td> + </tr> + + <tr> + <td> p<a href="#offsetspec">OFFSET</a>,SECONDS </td> + <td> + Pause for SECONDS seconds after OFFSET bytes. SECONDS can be an integer or "f" to + pause forever. + </td> + </tr> + + <tr> + <td> r<a href="#valuespec">VALUE</a> </td> + <td> + Set the raw frame payload. This disables masking, even if the key is present. + </td> + </tr> + + <tr> + <td> [-]rsv1 </td> + <td> + Set or un-set the <b>rsv1</b> bit. + </td> + </tr> + + <tr> + <td> [-]rsv2 </td> + <td> + Set or un-set the <b>rsv2</b> bit. + </td> + </tr> + + <tr> + <td> [-]rsv3 </td> + <td> + Set or un-set the <b>rsv3</b> bit. + </td> + </tr> + + <tr> + <td> x<a href="#valuespec">INTEGER</a> </td> + <td> + Repeat this message N times. + </td> + </tr> + + </tbody> +</table> diff --git a/pathod/libpathod/templates/docs_libpathod.html b/pathod/libpathod/templates/docs_libpathod.html new file mode 100644 index 00000000..6d504fe5 --- /dev/null +++ b/pathod/libpathod/templates/docs_libpathod.html @@ -0,0 +1,23 @@ +{% extends "docframe.html" %} {% block body %} +<div class="page-header"> + <h1> + libpathod + <small>Using pathod and pathoc in code.</small> + </h1> +</div> + +<div class="row"> + <div class="span6"> + <p> + Behind the pathod and pathoc command-line tools lurks <b>libpathod</b>, + a powerful library for manipulating and serving HTTP requests and responses. + The canonical documentation for the library is in the code, and can be + accessed using pydoc. + </p> + </div> + <div class="span6"> + <h1>pathoc</h1> + {% include "libpathod_pathoc.html" %} + </div> +</div> +{% endblock %} diff --git a/pathod/libpathod/templates/docs_pathoc.html b/pathod/libpathod/templates/docs_pathoc.html new file mode 100644 index 00000000..d38c3a77 --- /dev/null +++ b/pathod/libpathod/templates/docs_pathoc.html @@ -0,0 +1,211 @@ +{% extends "docframe.html" %} {% block body %} +<div class="page-header"> + <h1> + pathoc + <small>A perverse HTTP client.</small> + </h1> +</div> + +<p> + Pathoc is a perverse HTTP daemon designed to let you craft almost any conceivable + HTTP request, including ones that creatively violate the standards. HTTP requests + are specified using a + <a href="/docs/language">small, terse language</a>, which pathod shares with + its server-side twin <a href="/docs/pathod">pathod</a>. To view pathoc's complete + range of options, use the command-line help: +</p> + +<pre class="terminal">pathoc --help</pre> + +<section> + <div class="page-header"> + <h1>Getting Started</h1> + </div> + + <p>The basic pattern for pathoc commands is as follows: </p> + + <pre class="terminal">pathoc hostname request [request ...]</pre> + + <p> + That is, we specify the hostname to connect to, followed by one or more requests. + Lets start with a simple example: + </p> + + <pre class="terminal"> + > pathoc google.com get:/ << 301 Moved Permanently: 219 bytes + </pre> + + <p> + Here, we make a GET request to the path / on port 80 of google.com. Pathoc's output + tells us that the server responded with a 301. We can tell pathoc to connect + using SSL, in which case the default port is changed to 443 (you can over-ride + the default port with the <b>-p</b> command-line option): + </p> + + <pre class="terminal"> + > pathoc -s google.com get:/ << 301 Moved Permanently: 219 bytes + </pre> +</section> + + +<section> + <div class="page-header"> + <h1>Multiple Requests</h1> + </div> + + <p> + There are two ways to tell pathoc to issue multiple requests. The first is to specify + them on the command-line, like so: + </p> + + <pre class="terminal"> + > pathoc google.com get:/ get:/ << 301 Moved Permanently: 219 bytes << + 301 Moved Permanently: 219 bytes + </pre> + + <p> + In this case, pathoc issues the specified requests over the same TCP connection - + so in the above example only one connection is made to google.com + </p> + + <p>The other way to issue multiple requets is to use the <b>-n</b> flag:</p> + + <pre class="terminal"> + > pathoc -n 2 google.com get:/ << 301 Moved Permanently: 219 bytes << 301 + Moved Permanently: 219 bytes + </pre> + + <p> + The output is identical, but two separate TCP connections are made to the upstream + server. These two specification styles can be combined: + </p> + + <pre class="terminal"> + > pathoc -n 2 google.com get:/ get:/ << 301 Moved Permanently: 219 bytes << + 301 Moved Permanently: 219 bytes << 301 Moved Permanently: 219 bytes << + 301 Moved Permanently: 219 bytes + </pre> + + <p>Here, two distinct TCP connections are made, with two requests issued over each.</p> +</section> + + +<section> + <div class="page-header"> + <h1>Basic Fuzzing</h1> + </div> + + <p> + The combination of pathoc's powerful request specification language and a few of + its command-line options makes for quite a powerful basic fuzzer. Here's + an example: + </p> + + <pre class="terminal"> + > pathoc -e -I 200 -t 2 -n 1000 localhost get:/:b@10:ir,@1 + </pre> + + <p> + The request specified here is a valid GET with a body consisting of 10 random bytes, + but with 1 random byte inserted in a random place. This could be in the headers, + in the initial request line, or in the body itself. There are a few things + to note here: + </p> + + <ul> + <li> + Corrupting the request in this way will often make the server enter a state where + it's awaiting more input from the client. This is where the + <b>-t</b> option comes in, which sets a timeout that causes pathoc to + disconnect after two seconds. + </li> + + <li> + The <b>-n</b> option tells pathoc to repeat the request 1000 times. + </li> + + <li> + The <b>-I</b> option tells pathoc to ignore HTTP 200 response codes. + You can use this to fine-tune what pathoc considers to be an exceptional + condition, and therefore log-worthy. + </li> + + <li> + The <b>-e</b> option tells pathoc to print an explanation of each logged + request, in the form of an expanded pathoc specification with all random + portions and automatic header additions resolved. This lets you precisely + replay a request that triggered an error. + </li> + </ul> +</section> + + +<section> + <div class="page-header"> + <h1>Interacting with Proxies</h1> + </div> + + <p> + Pathoc has a reasonably sophisticated suite of features for interacting with proxies. + The proxy request syntax very closely mirrors that of straight HTTP, which + means that it is possible to make proxy-style requests using pathoc without + any additional syntax, by simply specifying a full URL instead of a simple + path: + </p> + + <pre class="terminal">> pathoc -p 8080 localhost "get:'http://google.com'"</pre> + + <p> + Another common use case is to use an HTTP CONNECT request to probe remote servers + via a proxy. This is done with the <b>-c</b> command-line option, + which allows you to specify a remote host and port pair: + </p> + + <pre class="terminal">> pathoc -c google.com:80 -p 8080 localhost get:/</pre> + + <p> + Note that pathoc does <b>not</b> negotiate SSL without being explictly instructed + to do so. If you're making a CONNECT request to an SSL-protected resource, + you must also pass the <b>-s</b> flag: + </p> + + <pre class="terminal">> pathoc -sc google.com:443 -p 8080 localhost get:/</pre> +</section> + + +<section> + <div class="page-header"> + <h1>Embedded response specification</h1> + </div> + + <p> + One interesting feature of the Request sppecification language is that you can embed + a response specifcation in it, which is then added to the request path. Here's + an example: + </p> + + <pre class="terminal">> pathoc localhost:9999 "get:/p/:s'401:ir,@1'"</pre> + + <p> + This crafts a request that connects to the pathod server, and which then crafts a + response that generates a 401, with one random byte embedded at a random + point. The response specification is parsed and expanded by pathoc, so you + see syntax errors immediately. This really becomes handy when combined with + the <b>-e</b> flag to show the expanded request: + </p> + + <pre class="terminal"> + > > pathoc -e localhost:9999 "get:/p/:s'401:ir,@1'" >> Spec: get:/p/:s'401:i15,\'o\':h\'Content-Length\'=\'0\'':h'Content-Length'='0' + << 401 Unoauthorized: 0 bytes </pre> + + <p> + Note that the embedded response has been resolved <i>before</i> being sent + to the server, so that "ir,@1" (embed a random byte at a random location) + has become "i15,\'o\'" (embed the character "o" at offset 15). You now have + a pathoc request specification that is precisely reproducable, even with + random components. This feature comes in terribly handy when testing a proxy, + since you can now drive the server repsonse completely from the client, and + have a complete log of reproducible requests to analyse afterwards. + </p> +</section> +{% endblock %} diff --git a/pathod/libpathod/templates/docs_pathod.html b/pathod/libpathod/templates/docs_pathod.html new file mode 100644 index 00000000..0d0ae933 --- /dev/null +++ b/pathod/libpathod/templates/docs_pathod.html @@ -0,0 +1,172 @@ +{% extends "docframe.html" %} {% block body %} +<div class="page-header"> + <h1> + pathod + <small>A pathological web daemon.</small> + </h1> +</div> + +<p> + Pathod is a pathological HTTP daemon designed to let you craft almost any conceivable + HTTP response, including ones that creatively violate the standards. HTTP responses + are specified using a + <a href="/docs/language">small, terse language</a>, which pathod shares with + its evil twin <a href="/docs/pathoc">pathoc</a>. +</p> + +<section> + <div class="page-header"> + <h1>Getting started</h1> + </div> + + <p>To start playing with pathod, simply fire up the daemon:</p> + + <pre class="terminal">./pathod</pre> + + <p> + By default, the service listens on port 9999 of localhost. Pathod's documentation + is self-hosting, and the pathod daemon exposes an interface that lets you + play with the specifciation language, preview what responses and requests + would look like on the wire, and view internal logs. To access all of this, + just fire up your browser, and point it to the following URL: + </p> + + <pre class="example">http://localhost:9999</pre> + + <p> + The default crafting anchor point is the path <b>/p/</b>. Anything after + this URL prefix is treated as a response specifier. So, hitting the following + URL will generate an HTTP 200 response with 100 bytes of random data: + </p> + + <pre class="example">http://localhost:9999/p/200:b@100</pre> + + <p> + See the <a href="/docs/language">language documentation</a> to get (much) + fancier. The pathod daemon also takes a range of configuration options. To + view those, use the command-line help: + </p> + + <pre class="terminal">./pathod --help</pre> + +</section> + +<section> + <div class="page-header"> + <h1>Acting as a proxy</h1> + </div> + + <p> + Pathod automatically responds to both straight HTTP and proxy requests. For proxy + requests, the upstream host is ignored, and the path portion of the URL is + used to match anchors. This lets you test software that supports a proxy + configuration by spoofing responses from upstream servers. + </p> + + <p> + By default, we treat all proxy CONNECT requests as HTTPS traffic, serving the response + using either pathod's built-in certificates, or the cert/key pair specified + by the user. You can over-ride this behaviour if you're testing a client + that makes a non-SSL CONNECT request using the -C command-line option. + </p> +</section> + + +<section> + <div class="page-header"> + <h1>Anchors</h1> + </div> + + <p> + Anchors provide an alternative to specifying the response in the URL. Instead, you + attach a response to a pre-configured anchor point, specified with a regex. + When a URL matching the regex is requested, the specified response is served. + </p> + + <pre class="terminal">./pathod -a "/foo=200"</pre> + + <p> + Here, "/foo" is the regex specifying the anchor path, and the part after the "=" + is a response specifier. + </p> +</section> + + +<section> + <div class="page-header"> + <h1>File Access</h1> + </div> + + <p> + There are two operators in the <a href="/docs/language">language</a> that + load contents from file - the <b>+</b> operator to load an entire request + specification from file, and the <b>></b> value specifier. In pathod, + both of these operators are restricted to a directory specified at startup, + or disabled if no directory is specified:</p> + <pre class="terminal">./pathod -d ~/staticdir"</pre> +</section> + + +<section> + <div class="page-header"> + <h1>Internal Error Responses</h1> + </div> + + <p> + Pathod uses the non-standard 800 response code to indicate internal errors, to distinguish + them from crafted responses. For example, a request to: + </p> + + <pre class="example">http://localhost:9999/p/foo</pre> + + <p> + ... will return an 800 response because "foo" is not a valid page specifier. + </p> +</section> + + +<section> + <div class="page-header"> + <h1>API</h1> + </div> + + <p> + pathod exposes a simple API, intended to make it possible to drive and inspect the + daemon remotely for use in unit testing and the like. + </p> + + <table class="table table-bordered"> + <tbody> + <tr> + <td> + /api/clear_log + </td> + <td> + A POST to this URL clears the log buffer. + </td> + </tr> + <tr> + <td> + /api/info + </td> + <td> + Basic version and configuration info. + </td> + </tr> + <tr> + <td> + /api/log + </td> + <td> + Returns the current log buffer. At the moment the buffer size is 500 entries - when + the log grows larger than this, older entries are discarded. + The returned data is a JSON dictionary, with the form: + + <pre>{ 'log': [ ENTRIES ] } </pre> You can preview the JSON data + returned for a log entry through the built-in web interface. + </td> + </tr> + </tbody> + </table> +</section> +{% endblock %} diff --git a/pathod/libpathod/templates/docs_test.html b/pathod/libpathod/templates/docs_test.html new file mode 100644 index 00000000..0502c984 --- /dev/null +++ b/pathod/libpathod/templates/docs_test.html @@ -0,0 +1,50 @@ +{% extends "docframe.html" %} {% block body %} +<div class="page-header"> + <h1> + libpathod.test + <small>Using libpathod in unit tests.</small> + </h1> +</div> + +<p>The <b>libpathod.test</b> module is a light, flexible testing layer for HTTP clients. + It works by firing up a Pathod instance in a separate thread, letting you use + Pathod's full abilities to generate responses, and then query Pathod's internal + logs to establish what happened. All the mechanics of startup, shutdown, finding + free ports and so forth are taken care of for you. +</p> + +<p>The canonical docs can be accessed using pydoc: </p> + +<pre class="terminal">pydoc libpathod.test</pre> + +<p> + The remainder of this page demonstrates some common interaction patterns using + <a href="http://nose.readthedocs.org/en/latest/">nose</a>. These examples are + also applicable with only minor modification to most commonly used Python testing + engines. +</p> + +<section> + <div class="page-header"> + <h1>Context Manager</h1> + </div> + + {% include "examples_context.html" %} +</section> + +<section> + <div class="page-header"> + <h1>One instance per test</h1> + </div> + + {% include "examples_setup.html" %} +</section> + +<section> + <div class="page-header"> + <h1>One instance per suite</h1> + </div> + + {% include "examples_setupall.html" %} +</section> +{% endblock %} diff --git a/pathod/libpathod/templates/download.html b/pathod/libpathod/templates/download.html new file mode 100644 index 00000000..bd8950e8 --- /dev/null +++ b/pathod/libpathod/templates/download.html @@ -0,0 +1,39 @@ +{% extends "frame.html" %} {% block body %} +<section> + <div class="page-header"> + <h1>pip</h1> + </div> + + <p>The easiest way to install pathod is to use pip:</p> + + <pre>pip install pathod</pre> + + <p> + This will automatically pull in all the dependencies, and you should be good to go. + </p> +</section> + +<section> + <div class="page-header"> + <h1>github</h1> + </div> + + <p>You can find the project source on GitHub:</p> + + <div style="margin-top: 20px; margin-bottom: 20px"> + <a class="btn btn-primary btn-large" href="https://github.com/mitmproxy/pathod">github.com/mitmproxy/pathod</a> + </div> + + <p>Please also use the <a href="https://github.com/mitmproxy/pathod/issues">github issue tracker</a> to report bugs.</p> +</section> + +<section> + <div class="page-header"> + <h1>tarball</h1> + </div> + + <div style="margin-top: 20px; margin-bottom: 20px"> + <a class="btn btn-primary btn-large" href="https://github.com/downloads/mitmproxy/pathod/pathod-{{version}}.tar.gz">pathod-{{version}}.tar.gz</a> + </div> +</section> +{% endblock %} diff --git a/pathod/libpathod/templates/examples_context.html b/pathod/libpathod/templates/examples_context.html new file mode 100644 index 00000000..afb3bd48 --- /dev/null +++ b/pathod/libpathod/templates/examples_context.html @@ -0,0 +1,24 @@ +<div class="highlight"><pre><span class="kn">import</span> <span class="nn">requests</span> +<span class="kn">from</span> <span class="nn">libpathod</span> <span class="kn">import</span> <span class="n">test</span> + + +<span class="k">def</span> <span class="nf">test_simple</span><span class="p">():</span> + <span class="sd">"""</span> +<span class="sd"> Testing the requests module with</span> +<span class="sd"> a pathod context manager.</span> +<span class="sd"> """</span> + <span class="c"># Start pathod in a separate thread</span> + <span class="k">with</span> <span class="n">test</span><span class="o">.</span><span class="n">Daemon</span><span class="p">()</span> <span class="k">as</span> <span class="n">d</span><span class="p">:</span> + <span class="c"># Get a URL for a pathod spec</span> + <span class="n">url</span> <span class="o">=</span> <span class="n">d</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s">"200:b@100"</span><span class="p">)</span> + <span class="c"># ... and request it</span> + <span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> + + <span class="c"># Check the returned data</span> + <span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span> + <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">content</span><span class="p">)</span> <span class="o">==</span> <span class="mi">100</span> + + <span class="c"># Check pathod's internal log</span> + <span class="n">log</span> <span class="o">=</span> <span class="n">d</span><span class="o">.</span><span class="n">last_log</span><span class="p">()[</span><span class="s">"request"</span><span class="p">]</span> + <span class="k">assert</span> <span class="n">log</span><span class="p">[</span><span class="s">"method"</span><span class="p">]</span> <span class="o">==</span> <span class="s">"PUT"</span> +</pre></div> diff --git a/pathod/libpathod/templates/examples_setup.html b/pathod/libpathod/templates/examples_setup.html new file mode 100644 index 00000000..c2da1cd1 --- /dev/null +++ b/pathod/libpathod/templates/examples_setup.html @@ -0,0 +1,32 @@ +<div class="highlight"><pre><span class="kn">import</span> <span class="nn">requests</span> +<span class="kn">from</span> <span class="nn">libpathod</span> <span class="kn">import</span> <span class="n">test</span> + + +<span class="k">class</span> <span class="nc">Test</span><span class="p">:</span> + + <span class="sd">"""</span> +<span class="sd"> Testing the requests module with</span> +<span class="sd"> a pathod instance started for</span> +<span class="sd"> each test.</span> +<span class="sd"> """</span> + + <span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> + <span class="bp">self</span><span class="o">.</span><span class="n">d</span> <span class="o">=</span> <span class="n">test</span><span class="o">.</span><span class="n">Daemon</span><span class="p">()</span> + + <span class="k">def</span> <span class="nf">tearDown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> + <span class="bp">self</span><span class="o">.</span><span class="n">d</span><span class="o">.</span><span class="n">shutdown</span><span class="p">()</span> + + <span class="k">def</span> <span class="nf">test_simple</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> + <span class="c"># Get a URL for a pathod spec</span> + <span class="n">url</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">d</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s">"200:b@100"</span><span class="p">)</span> + <span class="c"># ... and request it</span> + <span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> + + <span class="c"># Check the returned data</span> + <span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span> + <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">content</span><span class="p">)</span> <span class="o">==</span> <span class="mi">100</span> + + <span class="c"># Check pathod's internal log</span> + <span class="n">log</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">d</span><span class="o">.</span><span class="n">last_log</span><span class="p">()[</span><span class="s">"request"</span><span class="p">]</span> + <span class="k">assert</span> <span class="n">log</span><span class="p">[</span><span class="s">"method"</span><span class="p">]</span> <span class="o">==</span> <span class="s">"PUT"</span> +</pre></div> diff --git a/pathod/libpathod/templates/examples_setupall.html b/pathod/libpathod/templates/examples_setupall.html new file mode 100644 index 00000000..629d11e0 --- /dev/null +++ b/pathod/libpathod/templates/examples_setupall.html @@ -0,0 +1,40 @@ +<div class="highlight"><pre><span class="kn">import</span> <span class="nn">requests</span> +<span class="kn">from</span> <span class="nn">libpathod</span> <span class="kn">import</span> <span class="n">test</span> + + +<span class="k">class</span> <span class="nc">Test</span><span class="p">:</span> + + <span class="sd">"""</span> +<span class="sd"> Testing the requests module with</span> +<span class="sd"> a single pathod instance started</span> +<span class="sd"> for the test suite.</span> +<span class="sd"> """</span> + <span class="nd">@classmethod</span> + <span class="k">def</span> <span class="nf">setUpAll</span><span class="p">(</span><span class="n">cls</span><span class="p">):</span> + <span class="n">cls</span><span class="o">.</span><span class="n">d</span> <span class="o">=</span> <span class="n">test</span><span class="o">.</span><span class="n">Daemon</span><span class="p">()</span> + + <span class="nd">@classmethod</span> + <span class="k">def</span> <span class="nf">tearDownAll</span><span class="p">(</span><span class="n">cls</span><span class="p">):</span> + <span class="n">cls</span><span class="o">.</span><span class="n">d</span><span class="o">.</span><span class="n">shutdown</span><span class="p">()</span> + + <span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> + <span class="c"># Clear the pathod logs between tests</span> + <span class="bp">self</span><span class="o">.</span><span class="n">d</span><span class="o">.</span><span class="n">clear_log</span><span class="p">()</span> + + <span class="k">def</span> <span class="nf">test_simple</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> + <span class="c"># Get a URL for a pathod spec</span> + <span class="n">url</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">d</span><span class="o">.</span><span class="n">p</span><span class="p">(</span><span class="s">"200:b@100"</span><span class="p">)</span> + <span class="c"># ... and request it</span> + <span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> + + <span class="c"># Check the returned data</span> + <span class="k">assert</span> <span class="n">r</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span> + <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">content</span><span class="p">)</span> <span class="o">==</span> <span class="mi">100</span> + + <span class="c"># Check pathod's internal log</span> + <span class="n">log</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">d</span><span class="o">.</span><span class="n">last_log</span><span class="p">()[</span><span class="s">"request"</span><span class="p">]</span> + <span class="k">assert</span> <span class="n">log</span><span class="p">[</span><span class="s">"method"</span><span class="p">]</span> <span class="o">==</span> <span class="s">"PUT"</span> + + <span class="k">def</span> <span class="nf">test_two</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> + <span class="k">assert</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">d</span><span class="o">.</span><span class="n">log</span><span class="p">()</span> +</pre></div> diff --git a/pathod/libpathod/templates/frame.html b/pathod/libpathod/templates/frame.html new file mode 100644 index 00000000..4223458d --- /dev/null +++ b/pathod/libpathod/templates/frame.html @@ -0,0 +1,7 @@ +{% extends "layout.html" %} {% block content %} +<div class="row"> + <div class="span12"> + {% block body %} {% endblock %} + </div> +</div> +{% endblock %} diff --git a/pathod/libpathod/templates/index.html b/pathod/libpathod/templates/index.html new file mode 100644 index 00000000..a85a4040 --- /dev/null +++ b/pathod/libpathod/templates/index.html @@ -0,0 +1,60 @@ +{% extends "frame.html" %} {% block body %} +<div class="masthead"> + <div class="container"> + <h1>pathod: pathological HTTP</h1> + + <p>Crafted malice for tormenting HTTP clients and servers</p> + + <img src="/static/torture.png"> + </div> +</div> + +<div class="row"> + <div class="span6"> + <div> + <h2><a href="/docs/pathod">pathod</a></h2> + + <p>A pathological web daemon.</p> + + {% include "response_previewform.html" %} + <br> + </div> + </div> + + <div class="span6"> + <div> + <h2><a href="/docs/pathoc">pathoc</a></h2> + + <p>A perverse HTTP client.</p> + + {% include "request_previewform.html" %} + </div> + </div> +</div> + +<section> + <div class="page-header"> + <h1>Install</h1> + </div> + <div class="row"> + <div class="span6"> + <div> + <h2>pip</h2> + + <pre>pip install pathod</pre> + </div> + </div> + + <div class="span6"> + <div> + <h2>source</h2> + + <ul> + <li>Current release: <a href="http://mitmproxy.org/download/pathod-{{version}}.tar.gz">pathod {{version}}</a></li> + <li>GitHub: <a href="https://github.com/mitmproxy/pathod">github.com/mitmproxy/pathod</a></li> + </ul> + </div> + </div> + </div> +</section> +{% endblock %} diff --git a/pathod/libpathod/templates/layout.html b/pathod/libpathod/templates/layout.html new file mode 100644 index 00000000..af2857b1 --- /dev/null +++ b/pathod/libpathod/templates/layout.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="utf-8"> + <title>pathod</title> + <link href="/static/bootstrap.min.css" rel="stylesheet"> + <link href="/static/pathod.css" rel="stylesheet"> + <link href="/static/syntax.css" rel="stylesheet"> + <script src="/static/jquery-1.7.2.min.js"></script> + <script src="/static/jquery.scrollTo-min.js"></script> + <script src="/static/jquery.localscroll-min.js"></script> + <script src="/static/bootstrap.min.js"></script> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content=""> + <meta name="author" content=""> + <style type="text/css"> + body { + padding-top: 60px; + padding-bottom: 40px; + } + + </style> + <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements --> + <!--[if lt IE 9]> + <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> + <![endif]--> +</head> + +<body> + <div class="navbar navbar-fixed-top"> + <div class="navbar-inner"> + <div class="container"> + <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </a> + <a class="brand" href="/index.html">pathod</a> + <div class="nav-collapse"> + <ul class="nav"> + <li {% if section=="main" %} class="active" {% endif %}><a href="/">home</a></li> + {% if not noapi %} + <li {% if section=="log" %} class="active" {% endif %}><a href="/log">log</a></li> + {% endif %} + <li {% if section=="docs" %} class="active" {% endif %}><a href="/docs/pathod">docs</a></li> + <li {% if section=="about" %} class="active" {% endif %}><a href="/about">about</a></li> + </ul> + </div> + </div> + </div> + </div> + + <div class="container"> + {% block content %} {% endblock %} + <hr> + <footer> + <span>© Aldo Cortesi 2015</span> + <span class="pull-right">[served with pathod]</span> + </footer> + </div> +</body> +<script> + $(function() { + $.localScroll({ + duration: 300, + offset: { + top: -45 + } + }); + }); + +</script> + +</html> diff --git a/pathod/libpathod/templates/libpathod_pathoc.html b/pathod/libpathod/templates/libpathod_pathoc.html new file mode 100644 index 00000000..f5871b16 --- /dev/null +++ b/pathod/libpathod/templates/libpathod_pathoc.html @@ -0,0 +1,8 @@ +<div class="highlight"><pre><span class="c">#!/usr/bin/env python</span> +<span class="kn">from</span> <span class="nn">libpathod</span> <span class="kn">import</span> <span class="n">pathoc</span> + +<span class="n">p</span> <span class="o">=</span> <span class="n">pathoc</span><span class="o">.</span><span class="n">Pathoc</span><span class="p">((</span><span class="s">"google.com"</span><span class="p">,</span> <span class="mi">80</span><span class="p">))</span> +<span class="n">p</span><span class="o">.</span><span class="n">connect</span><span class="p">()</span> +<span class="k">print</span> <span class="n">p</span><span class="o">.</span><span class="n">request</span><span class="p">(</span><span class="s">"get:/"</span><span class="p">)</span> +<span class="k">print</span> <span class="n">p</span><span class="o">.</span><span class="n">request</span><span class="p">(</span><span class="s">"get:/foo"</span><span class="p">)</span> +</pre></div> diff --git a/pathod/libpathod/templates/log.html b/pathod/libpathod/templates/log.html new file mode 100644 index 00000000..b0484cb8 --- /dev/null +++ b/pathod/libpathod/templates/log.html @@ -0,0 +1,31 @@ +{% extends "frame.html" %} {% block body %} +<form style="float: right" method="POST" action="/log/clear"> + <button type="submit" class="btn">clear</button> +</form> + +<h1>Logs</h1> +<hr> + +<table class="table table-striped table-condensed"> + <thead> + <tr> + <th>id</th> + <th>method</th> + <th>path</th> + </tr> + </thead> + <tbody> + {% for i in log %} + <tr> + {% if i["type"] == 'error' %} + <td colspan="3">ERROR: {{ i["msg"] }}</td> + {% else %} + <td>{{ i["id"] }}</td> + <td>{{ i["request"]["method"] }}</td> + <td><a href="/log/{{ i[" id "] }}">{{ i["request"]["path"] }}</a></td> + {% endif %} + </tr> + {% endfor %} + </tbody> +</table> +{% endblock %} diff --git a/pathod/libpathod/templates/onelog.html b/pathod/libpathod/templates/onelog.html new file mode 100644 index 00000000..c222ad60 --- /dev/null +++ b/pathod/libpathod/templates/onelog.html @@ -0,0 +1,8 @@ +{% extends "frame.html" %} {% block body %} +<h2>Log entry {{ lid }}</h2> +<hr> + +<pre> + {{ alog }} +</pre> +{% endblock %} diff --git a/pathod/libpathod/templates/request_preview.html b/pathod/libpathod/templates/request_preview.html new file mode 100644 index 00000000..25d73679 --- /dev/null +++ b/pathod/libpathod/templates/request_preview.html @@ -0,0 +1,44 @@ +{% extends "frame.html" %} {% block body %} +<div class="page-header"> + <h1>pathoc preview</h1> +</div> + +<div style="margin-bottom: 20px" class="row"> + <div class="span2 header"> + Specification: + </div> + <div class="span10"> + {% include "request_previewform.html" %} + </div> +</div> + +{% if syntaxerror %} +<div class="row"> + <div class="span2 header"> + Error: + </div> + <div class="span10"> + <p style="color: #ff0000">{{ syntaxerror }}</p> + <pre>{{ marked }}</pre> + </div> +</div> +{% elif error %} +<div class="row"> + <div class="span2 header"> + Error: + </div> + <div class="span10"> + <p style="color: #ff0000">{{ error }}</p> + </div> +</div> +{% else %} +<div class="row"> + <div class="span2 header"> + Request: + </div> + <div class="span10"> + <pre>{{ output }}</pre> + <p>Note: pauses are skipped when generating previews!</p> + </div> +</div> +{% endif %} {% endblock %} diff --git a/pathod/libpathod/templates/request_previewform.html b/pathod/libpathod/templates/request_previewform.html new file mode 100644 index 00000000..91b5598a --- /dev/null +++ b/pathod/libpathod/templates/request_previewform.html @@ -0,0 +1,53 @@ +<form style="margin-bottom: 0" class="form-inline" method="GET" action="/request_preview"> + <input style="width: 18em" id="spec" name="spec" class="input-medium" value="{{spec}}" + placeholder="method:path:[features]"> + <input type="submit" class="btn" value="preview"> +</form> + +<a class="innerlink" data-toggle="collapse" data-target="#requestexamples">examples</a> + +<div id="requestexamples" class="collapse"> + <p> + Check out the <a href="/docs/language">complete language docs</a>. Here are + some examples to get you started: + </p> + + <table class="table table-bordered"> + <tbody> + <tr> + <td><a href="/request_preview?spec=get:/">get:/</a></td> + <td>Get path /</td> + </tr> + <tr> + <td><a href="/request_preview?spec=get:/:b@100">get:/:b@100</a></td> + <td>100 random bytes as the body</td> + </tr> + <tr> + <td><a href='/request_preview?spec=get:/:h"Etag"="';drop table browsers;"'>get:/:h"Etag"="';drop table browsers;"</a></td> + <td>Add a header</td> + </tr> + <tr> + <td><a href='/request_preview?spec=get:/:u"';drop table browsers;"'>get:/:u"';drop table browsers;"</a></td> + <td>Add a User-Agent header</td> + </tr> + <tr> + <td><a href="/request_preview?spec=get:/:b@100:dr">get:/:b@100:dr</a></td> + <td>Drop the connection randomly</td> + </tr> + <tr> + <td> + <a href="/request_preview?spec="></a> + </td> + <td></td> + </tr> + <tr> + <td><a href="/request_preview?spec=get:/:b@100,ascii:ir,@1">get:/:b@100,ascii:ir,@1</a></td> + <td>100 ASCII bytes as the body, and randomly inject a random byte</td> + </tr> + <tr> + <td><a href="/request_preview?spec=ws:/">ws:/</a></td> + <td>Initiate a websocket handshake.</td> + </tr> + </tbody> + </table> +</div> diff --git a/pathod/libpathod/templates/response_preview.html b/pathod/libpathod/templates/response_preview.html new file mode 100644 index 00000000..bbce6d6c --- /dev/null +++ b/pathod/libpathod/templates/response_preview.html @@ -0,0 +1,44 @@ +{% extends "frame.html" %} {% block body %} +<div class="page-header"> + <h1>pathod preview</h1> +</div> + +<div style="margin-bottom: 20px" class="row"> + <div class="span2 header"> + Specification: + </div> + <div class="span10"> + {% include "response_previewform.html" %} + </div> +</div> + +{% if syntaxerror %} +<div class="row"> + <div class="span2 header"> + Error: + </div> + <div class="span10"> + <p style="color: #ff0000">{{ syntaxerror }}</p> + <pre>{{ marked }}</pre> + </div> +</div> +{% elif error %} +<div class="row"> + <div class="span2 header"> + Error: + </div> + <div class="span10"> + <p style="color: #ff0000">{{ error }}</p> + </div> +</div> +{% else %} +<div class="row"> + <div class="span2 header"> + Response: + </div> + <div class="span10"> + <pre>{{ output }}</pre> + <p>Note: pauses are skipped when generating previews!</p> + </div> +</div> +{% endif %} {% endblock %} diff --git a/pathod/libpathod/templates/response_previewform.html b/pathod/libpathod/templates/response_previewform.html new file mode 100644 index 00000000..d46043f3 --- /dev/null +++ b/pathod/libpathod/templates/response_previewform.html @@ -0,0 +1,87 @@ +<form style="margin-bottom: 0" class="form-inline" method="GET" action="/response_preview"> + <input style="width: 18em" id="spec" name="spec" class="input-medium" value="{{spec}}" + placeholder="code:[features]"> + <input type="submit" class="btn" value="preview"> + {% if not nocraft %} + <a href="#" id="submitspec" class="btn">go</a> + {% endif %} +</form> + +<a class="innerlink" data-toggle="collapse" data-target="#responseexamples">examples</a> + +<div id="responseexamples" class="collapse"> + <p> + Check out the <a href="/docs/language">complete language docs</a>. Here are + some examples to get you started: + </p> + + <table class="table table-bordered"> + <tbody> + <tr> + <td><a href="/response_preview?spec=200">200</a></td> + <td>A basic HTTP 200 response.</td> + </tr> + <tr> + <td><a href="/response_preview?spec=200:r">200:r</a></td> + <td>A basic HTTP 200 response with no Content-Length header. This will + hang. + </td> + </tr> + <tr> + <td><a href="/response_preview?spec=200:da">200:da</a></td> + <td>Server-side disconnect after all content has been sent.</td> + </tr> + <tr> + <td><a href="/response_preview?spec=200:b@100">200:b@100</a></td> + <td> + 100 random bytes as the body. A Content-Lenght header is added, so the disconnect + is no longer needed. + </td> + </tr> + <tr> + <td><a href='/response_preview?spec=200:b@100:h"Server"="';drop table servers;"'>200:b@100:h"Etag"="';drop table servers;"</a></td> + <td>Add a Server header</td> + </tr> + <tr> + <td><a href="/response_preview?spec=200:b@100:dr">200:b@100:dr</a></td> + <td>Drop the connection randomly</td> + </tr> + <tr> + <td><a href="/response_preview?spec=200:b@100,ascii:ir,@1">200:b@100,ascii:ir,@1</a></td> + <td>100 ASCII bytes as the body, and randomly inject a random byte</td> + </tr> + <tr> + <td><a href='/response_preview?spec=200:b@1k:c"text/json"'>200:b@1k:c"text/json"</a></td> + <td>1k of random bytes, with a text/json content type</td> + </tr> + <tr> + <td><a href='/response_preview?spec=200:b@1k:p50,120'>200:b@1k:p50,120</a></td> + <td>1k of random bytes, pause for 120 seconds after 50 bytes</td> + </tr> + <tr> + <td><a href='/response_preview?spec=200:b@1k:pr,f'>200:b@1k:pr,f</a></td> + <td>1k of random bytes, but hang forever at a random location</td> + </tr> + <tr> + <td> + <a href="/response_preview?spec=200:b@100:h@1k,ascii_letters='foo'">200:b@100:h@1k,ascii_letters='foo'</a> + </td> + <td> + 100 ASCII bytes as the body, randomly generated 100k header name, with the value + 'foo'. + </td> + </tr> + </tbody> + </table> +</div> + +{% if not nocraft %} +<script> + $(function() { + $("#submitspec").click(function() { + document.location = "{{craftanchor}}" + $("#spec").val() + }); + }); + +</script> +{% endif %} diff --git a/pathod/libpathod/test.py b/pathod/libpathod/test.py new file mode 100644 index 00000000..33a6b763 --- /dev/null +++ b/pathod/libpathod/test.py @@ -0,0 +1,103 @@ +import cStringIO +import threading +import Queue + +import requests +import requests.packages.urllib3 +from . import pathod + +requests.packages.urllib3.disable_warnings() + + +class Daemon: + IFACE = "127.0.0.1" + + def __init__(self, ssl=None, **daemonargs): + self.q = Queue.Queue() + self.logfp = cStringIO.StringIO() + daemonargs["logfp"] = self.logfp + self.thread = _PaThread(self.IFACE, self.q, ssl, daemonargs) + self.thread.start() + self.port = self.q.get(True, 5) + self.urlbase = "%s://%s:%s" % ( + "https" if ssl else "http", + self.IFACE, + self.port + ) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.logfp.truncate(0) + self.shutdown() + return False + + def p(self, spec): + """ + Return a URL that will render the response in spec. + """ + return "%s/p/%s" % (self.urlbase, spec) + + def info(self): + """ + Return some basic info about the remote daemon. + """ + resp = requests.get("%s/api/info" % self.urlbase, verify=False) + return resp.json() + + def text_log(self): + return self.logfp.getvalue() + + def last_log(self): + """ + Returns the last logged request, or None. + """ + l = self.log() + if not l: + return None + return l[0] + + def log(self): + """ + Return the log buffer as a list of dictionaries. + """ + resp = requests.get("%s/api/log" % self.urlbase, verify=False) + return resp.json()["log"] + + def clear_log(self): + """ + Clear the log. + """ + self.logfp.truncate(0) + resp = requests.get("%s/api/clear_log" % self.urlbase, verify=False) + return resp.ok + + def shutdown(self): + """ + Shut the daemon down, return after the thread has exited. + """ + self.thread.server.shutdown() + self.thread.join() + + +class _PaThread(threading.Thread): + + def __init__(self, iface, q, ssl, daemonargs): + threading.Thread.__init__(self) + self.name = "PathodThread" + self.iface, self.q, self.ssl = iface, q, ssl + self.daemonargs = daemonargs + + def run(self): + self.server = pathod.Pathod( + (self.iface, 0), + ssl=self.ssl, + **self.daemonargs + ) + self.name = "PathodThread (%s:%s)" % ( + self.server.address.host, + self.server.address.port + ) + self.q.put(self.server.address.port) + self.server.serve_forever() diff --git a/pathod/libpathod/utils.py b/pathod/libpathod/utils.py new file mode 100644 index 00000000..a1109a3c --- /dev/null +++ b/pathod/libpathod/utils.py @@ -0,0 +1,124 @@ +import os +import sys + + +SIZE_UNITS = dict( + b=1024 ** 0, + k=1024 ** 1, + m=1024 ** 2, + g=1024 ** 3, + t=1024 ** 4, +) + + +class MemBool(object): + + """ + Truth-checking with a memory, for use in chained if statements. + """ + + def __init__(self): + self.v = None + + def __call__(self, v): + self.v = v + return bool(v) + + +def parse_size(s): + try: + return int(s) + except ValueError: + pass + for i in SIZE_UNITS.keys(): + if s.endswith(i): + try: + return int(s[:-1]) * SIZE_UNITS[i] + except ValueError: + break + raise ValueError("Invalid size specification.") + + +def parse_anchor_spec(s): + """ + Return a tuple, or None on error. + """ + if "=" not in s: + return None + return tuple(s.split("=", 1)) + + +def xrepr(s): + return repr(s)[1:-1] + + +def inner_repr(s): + """ + Returns the inner portion of a string or unicode repr (i.e. without the + quotes) + """ + if isinstance(s, unicode): + return repr(s)[2:-1] + else: + return repr(s)[1:-1] + + +def escape_unprintables(s): + """ + Like inner_repr, but preserves line breaks. + """ + s = s.replace("\r\n", "PATHOD_MARKER_RN") + s = s.replace("\n", "PATHOD_MARKER_N") + s = inner_repr(s) + s = s.replace("PATHOD_MARKER_RN", "\n") + s = s.replace("PATHOD_MARKER_N", "\n") + return s + + +class Data(object): + + def __init__(self, name): + m = __import__(name) + dirname, _ = os.path.split(m.__file__) + self.dirname = os.path.abspath(dirname) + + def path(self, path): + """ + Returns a path to the package data housed at 'path' under this + module.Path can be a path to a file, or to a directory. + + This function will raise ValueError if the path does not exist. + """ + fullpath = os.path.join(self.dirname, path) + if not os.path.exists(fullpath): + raise ValueError("dataPath: %s does not exist." % fullpath) + return fullpath + + +data = Data(__name__) + + +def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): # pragma: nocover + try: + pid = os.fork() + if pid > 0: + sys.exit(0) + except OSError as e: + sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror)) + sys.exit(1) + os.chdir("/") + os.umask(0) + os.setsid() + try: + pid = os.fork() + if pid > 0: + sys.exit(0) + except OSError as e: + sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror)) + sys.exit(1) + si = open(stdin, 'rb') + so = open(stdout, 'a+b') + se = open(stderr, 'a+b', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) diff --git a/pathod/libpathod/version.py b/pathod/libpathod/version.py new file mode 100644 index 00000000..b99ae40b --- /dev/null +++ b/pathod/libpathod/version.py @@ -0,0 +1,11 @@ +from __future__ import (absolute_import, print_function, division) + +IVERSION = (0, 17) +VERSION = ".".join(str(i) for i in IVERSION) +MINORVERSION = ".".join(str(i) for i in IVERSION[:2]) +NAME = "pathod" +NAMEVERSION = NAME + " " + VERSION + +NEXT_MINORVERSION = list(IVERSION) +NEXT_MINORVERSION[1] += 1 +NEXT_MINORVERSION = ".".join(str(i) for i in NEXT_MINORVERSION[:2]) diff --git a/pathod/pathoc b/pathod/pathoc new file mode 100755 index 00000000..b3121611 --- /dev/null +++ b/pathod/pathoc @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +from libpathod import pathoc_cmdline as cmdline + +if __name__ == "__main__": + cmdline.go_pathoc() diff --git a/pathod/pathod b/pathod/pathod new file mode 100755 index 00000000..a79becf1 --- /dev/null +++ b/pathod/pathod @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +from libpathod import pathod_cmdline as cmdline + +if __name__ == "__main__": + cmdline.go_pathod() diff --git a/pathod/release/pathoc.spec b/pathod/release/pathoc.spec new file mode 100644 index 00000000..22649076 --- /dev/null +++ b/pathod/release/pathoc.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +from PyInstaller.utils.hooks import collect_data_files + +a = Analysis(['../pathoc'], + binaries=None, + datas=None, + hiddenimports=['_cffi_backend'], + hookspath=None, + runtime_hooks=None, + excludes=None) +pyz = PYZ(a.pure, a.zipped_data) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name='pathoc', + debug=False, + strip=None, + upx=True, + console=True ) diff --git a/pathod/release/pathod.spec b/pathod/release/pathod.spec new file mode 100644 index 00000000..706b6b68 --- /dev/null +++ b/pathod/release/pathod.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +from PyInstaller.utils.hooks import collect_data_files + +a = Analysis(['../pathod'], + binaries=None, + datas=collect_data_files("libpathod"), + hiddenimports=['_cffi_backend'], + hookspath=None, + runtime_hooks=None, + excludes=None) +pyz = PYZ(a.pure, a.zipped_data) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name='pathod', + debug=False, + strip=None, + upx=True, + console=True ) diff --git a/pathod/requirements.txt b/pathod/requirements.txt new file mode 100644 index 00000000..69bd183b --- /dev/null +++ b/pathod/requirements.txt @@ -0,0 +1,2 @@ +-e git+https://github.com/mitmproxy/netlib.git#egg=netlib +-e .[dev]
\ No newline at end of file diff --git a/pathod/setup.py b/pathod/setup.py new file mode 100644 index 00000000..ff50bdd0 --- /dev/null +++ b/pathod/setup.py @@ -0,0 +1,63 @@ +from setuptools import setup, find_packages +from codecs import open +import os +from libpathod import version + +# Based on https://github.com/pypa/sampleproject/blob/master/setup.py +# and https://python-packaging-user-guide.readthedocs.org/ + +here = os.path.abspath(os.path.dirname(__file__)) + +with open(os.path.join(here, 'README.txt'), encoding='utf-8') as f: + long_description = f.read() + +setup( + name="pathod", + version=version.VERSION, + description="A pathological HTTP/S daemon for testing and stressing clients.", + long_description=long_description, + url="http://pathod.net", + author="Aldo Cortesi", + author_email="aldo@corte.si", + license="MIT", + classifiers=[ + "License :: OSI Approved :: MIT License", + "Development Status :: 5 - Production/Stable", + "Operating System :: POSIX", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Internet", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: HTTP Servers", + "Topic :: Software Development :: Testing", + "Topic :: Software Development :: Testing :: Traffic Generation", + ], + + packages=find_packages(exclude=["test", "test.*"]), + include_package_data=True, + entry_points={ + 'console_scripts': [ + "pathod = libpathod.pathod_cmdline:go_pathod", + "pathoc = libpathod.pathoc_cmdline:go_pathoc" + ] + }, + install_requires=[ + "netlib>=%s, <%s" % (version.MINORVERSION, version.NEXT_MINORVERSION), + "requests>=2.9.1, <2.10", + "Flask>=0.10.1, <0.11", + "pyparsing>=2.1,<2.2" + ], + extras_require={ + 'dev': [ + "mock>=1.3.0, <1.4", + "pytest>=2.8.0", + "pytest-xdist>=1.14, <1.15", + "pytest-cov>=2.2.1, <2.3", + "pytest-timeout>=1.0.0, <1.1", + "coveralls>=1.1, <1.2" + ] + } +) diff --git a/pathod/test/data/clientcert/.gitignore b/pathod/test/data/clientcert/.gitignore new file mode 100644 index 00000000..07bc53d2 --- /dev/null +++ b/pathod/test/data/clientcert/.gitignore @@ -0,0 +1,3 @@ +client.crt +client.key +client.req diff --git a/pathod/test/data/clientcert/client.cnf b/pathod/test/data/clientcert/client.cnf new file mode 100644 index 00000000..5046a944 --- /dev/null +++ b/pathod/test/data/clientcert/client.cnf @@ -0,0 +1,5 @@ +[ ssl_client ] +basicConstraints = CA:FALSE +nsCertType = client +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = clientAuth diff --git a/pathod/test/data/clientcert/client.pem b/pathod/test/data/clientcert/client.pem new file mode 100644 index 00000000..4927bca2 --- /dev/null +++ b/pathod/test/data/clientcert/client.pem @@ -0,0 +1,42 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAzCpoRjSTfIN24kkNap/GYmP9zVWj0Gk8R5BB/PvvN0OB1Zk0 +EEYPsWCcuhEdK0ehiDZX030doF0DOncKKa6mop/d0x2o+ts42peDhZM6JNUrm6d+ +ZWQVtio33mpp77UMhR093vaA+ExDnmE26kBTVijJ1+fRAVDXG/cmQINEri91Kk/G +3YJ5e45UrohGI5seBZ4vV0xbHtmczFRhYFlGOvYsoIe4Lvz/eFS2pIrTIpYQ2VM/ +SQQl+JFy+NlQRsWG2NrxtKOzMnnDE7YN4I3z5D5eZFo1EtwZ48LNCeSwrEOdfuzP +G5q5qbs5KpE/x85H9umuRwSCIArbMwBYV8a8JwIDAQABAoIBAFE3FV/IDltbmHEP +iky93hbJm+6QgKepFReKpRVTyqb7LaygUvueQyPWQMIriKTsy675nxo8DQr7tQsO +y3YlSZgra/xNMikIB6e82c7K8DgyrDQw/rCqjZB3Xt4VCqsWJDLXnQMSn98lx0g7 +d7Lbf8soUpKWXqfdVpSDTi4fibSX6kshXyfSTpcz4AdoncEpViUfU1xkEEmZrjT8 +1GcCsDC41xdNmzCpqRuZX7DKSFRoB+0hUzsC1oiqM7FD5kixonRd4F5PbRXImIzt +6YCsT2okxTA04jX7yByis7LlOLTlkmLtKQYuc3erOFvwx89s4vW+AeFei+GGNitn +tHfSwbECgYEA7SzV+nN62hAERHlg8cEQT4TxnsWvbronYWcc/ev44eHSPDWL5tPi +GHfSbW6YAq5Wa0I9jMWfXyhOYEC3MZTC5EEeLOB71qVrTwcy/sY66rOrcgjFI76Q +5JFHQ4wy3SWU50KxE0oWJO9LIowprG+pW1vzqC3VF0T7q0FqESrY4LUCgYEA3F7Z +80ndnCUlooJAb+Hfotv7peFf1o6+m1PTRcz1lLnVt5R5lXj86kn+tXEpYZo1RiGR +2rE2N0seeznWCooakHcsBN7/qmFIhhooJNF7yW+JP2I4P2UV5+tJ+8bcs/voUkQD +1x+rGOuMn8nvHBd2+Vharft8eGL2mgooPVI2XusCgYEAlMZpO3+w8pTVeHaDP2MR +7i/AuQ3cbCLNjSX3Y7jgGCFllWspZRRIYXzYPNkA9b2SbBnTLjjRLgnEkFBIGgvs +7O2EFjaCuDRvydUEQhjq4ErwIsopj7B8h0QyZcbOKTbn3uFQ3n68wVJx2Sv/ADHT +FIHrp/WIE96r19Niy34LKXkCgYB2W59VsuOKnMz01l5DeR5C+0HSWxS9SReIl2IO +yEFSKullWyJeLIgyUaGy0990430feKI8whcrZXYumuah7IDN/KOwzhCk8vEfzWao +N7bzfqtJVrh9HA7C7DVlO+6H4JFrtcoWPZUIomJ549w/yz6EN3ckoMC+a/Ck1TW9 +ka1QFwKBgQCywG6TrZz0UmOjyLQZ+8Q4uvZklSW5NAKBkNnyuQ2kd5rzyYgMPE8C +Er8T88fdVIKvkhDyHhwcI7n58xE5Gr7wkwsrk/Hbd9/ZB2GgAPY3cATskK1v1McU +YeX38CU0fUS4aoy26hWQXkViB47IGQ3jWo3ZCtzIJl8DI9/RsBWTnw== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICYDCCAckCAQEwDQYJKoZIhvcNAQEFBQAwKDESMBAGA1UEAxMJbWl0bXByb3h5 +MRIwEAYDVQQKEwltaXRtcHJveHkwHhcNMTMwMTIwMDEwODEzWhcNMTUxMDE3MDEw +ODEzWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UE +ChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAzCpoRjSTfIN24kkNap/GYmP9zVWj0Gk8R5BB/PvvN0OB1Zk0 +EEYPsWCcuhEdK0ehiDZX030doF0DOncKKa6mop/d0x2o+ts42peDhZM6JNUrm6d+ +ZWQVtio33mpp77UMhR093vaA+ExDnmE26kBTVijJ1+fRAVDXG/cmQINEri91Kk/G +3YJ5e45UrohGI5seBZ4vV0xbHtmczFRhYFlGOvYsoIe4Lvz/eFS2pIrTIpYQ2VM/ +SQQl+JFy+NlQRsWG2NrxtKOzMnnDE7YN4I3z5D5eZFo1EtwZ48LNCeSwrEOdfuzP +G5q5qbs5KpE/x85H9umuRwSCIArbMwBYV8a8JwIDAQABMA0GCSqGSIb3DQEBBQUA +A4GBAFvI+cd47B85PQ970n2dU/PlA2/Hb1ldrrXh2guR4hX6vYx/uuk5yRI/n0Rd +KOXJ3czO0bd2Fpe3ZoNpkW0pOSDej/Q+58ScuJd0gWCT/Sh1eRk6ZdC0kusOuWoY +bPOPMkG45LPgUMFOnZEsfJP6P5mZIxlbCvSMFC25nPHWlct7 +-----END CERTIFICATE----- diff --git a/pathod/test/data/clientcert/make b/pathod/test/data/clientcert/make new file mode 100755 index 00000000..d1caea81 --- /dev/null +++ b/pathod/test/data/clientcert/make @@ -0,0 +1,8 @@ +#!/bin/sh + +openssl genrsa -out client.key 2048 +openssl req -key client.key -new -out client.req +openssl x509 -req -days 365 -in client.req -signkey client.key -out client.crt -extfile client.cnf -extensions ssl_client +openssl x509 -req -days 1000 -in client.req -CA ~/.mitmproxy/mitmproxy-ca.pem -CAkey ~/.mitmproxy/mitmproxy-ca.pem -set_serial 00001 -out client.crt -extensions ssl_client +cat client.key client.crt > client.pem +openssl x509 -text -noout -in client.pem diff --git a/pathod/test/data/file b/pathod/test/data/file new file mode 100644 index 00000000..26918572 --- /dev/null +++ b/pathod/test/data/file @@ -0,0 +1 @@ +testfile diff --git a/pathod/test/data/request b/pathod/test/data/request new file mode 100644 index 00000000..c4c90e76 --- /dev/null +++ b/pathod/test/data/request @@ -0,0 +1 @@ +get:/foo diff --git a/pathod/test/data/response b/pathod/test/data/response new file mode 100644 index 00000000..8f897c85 --- /dev/null +++ b/pathod/test/data/response @@ -0,0 +1 @@ +202 diff --git a/pathod/test/data/testkey.pem b/pathod/test/data/testkey.pem new file mode 100644 index 00000000..b804bd4c --- /dev/null +++ b/pathod/test/data/testkey.pem @@ -0,0 +1,68 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIG5QIBAAKCAYEAwvtKxoZvBV2AxPAkCx8PXbuE7KeqK9bBvk8x+JchPMdf/KZj +sdu2v6Gm8Hi053i7ZGxouFvonJxHAiK6cwk9OYQwa9fbOFf2mgWKEBO4fbCH93tW +DCTdWVxFyNViAvxGHlJs3/IU03pIG29AgUnhRW8pGbabAfx8emcOZJZ3ykEuimaC +4s7mRwdc63GXnbcjTtRkrJsBATI+xvPwuR2+4daX7sPCf0kel3bN2jMpwXfvk/Ww +kJ2BIEeZCg0qIvyMjH9qrUirUnsmQnpPln0CGBbQEBsW9yMfGoFdREiMYls5jZeq +NxjWNv1RTRIm/4RjMwyxnoTA9eDS9wwO2NnJS4vfXAnUTP4BYx8Pe4ZMA2Gm6YrC +ysT6YA1xdHNpcuHXClxwmPj/cm8Z5kIg5clbNIK60ts9yFr/Ao3KPPYJ2GBv8/Oe +ApPBJuubews+/9/13Ew/SJ1t2u28+sPbgXUG8dC2n4vWTvJwKf6Duqxgnm82zdzj +SZoXRQsP984qiN7NAgMBAAECggGBALB6rqWdzCL5DLI0AQun40qdjaR95UKksNvF +5p7we379nl2ZZKb5DSHJ+MWzG1pfJo2wqeAkIBiQQp0mPcgdVrMWeJVD3QHUbDng +RaRjlRr+izJvCeUYANj+8ZLjwECfgf+z7yOLg1oeVeGvAp2C90jXYkYJx6c2lpxb +ZuWYY3hHIw7V1iXfywIDIhFg0TBJMMYK68xmx7QDfFqrNPj4eWsDxqSvvv1iezPw +rkWPBX49RjWPrW5XgSZsZ5J3c+oS1rZmIY7EAgopTWB/3wJjZR1Idz/9l9LIWlBP +6zVC27CIZzSEeGguqNVeyzJ0TPWh5idYNRmSZr6eTUF0245LNO/gqvWKgRSNIZko +HoBa2F1AvCiB67S1kxjwS5y3VkudZE4jkgGKcC2Ws/9QmOZ0HAsjI8VAMp2hj6iN +0HdPMTNtsLgbhKyXsoZuW4YmwfSTPxGi2gvcI7GUozpTz84n1cOneJnz1ygx6Uru +v8DpQg+VX6xTy4X6AK1F8OYNMZ/jaQKBwQDv30NevQStnGbTmcSr+0fd4rmWFklK +V6B2X7zWynVpSGvCWkqVSp3mG6aiZItAltVMRL/9LT6zIheyClyd+vXIjR2+W210 +XMxrvz7A8qCXkvB2dyEhrMdCfZ7p8+kf+eD2c/Mnxb7VpmDfHYLx30JeQoBwjrwU +Eul+dE1P+r8bWBaLTjlsipTya74yItWWAToXAo+s1BXBtXhEsLoe4FghlC0u724d +ucjDaeICdLcerApdvg6Q6p4kVHaoF6ka6I8CgcEA0Bdc05ery9gLC6CclV+BhA5Q +dfDq2P7qhc7e1ipwNRrQo2gy5HhgOkTL3dJWc+8rV6CBP/JfchnsW40tDOnPCTLT +gg3n7vv3RHrtncApXuhIFR+B5xjohTPBzxRUMiAOre2d0F5b6eBXFjptf/1i2tQ+ +qdqJoyOGOZP0hKVslGIfz+CKc6WEkIqX7c91Msdr5myeaWDI5TsurfuKRBH395T3 +BMAi6oinAAEb1rdySenLO2A/0kVmBVlTpaN3TNjjAoHBAMvS4uQ1qSv8okNbfgrF +UqPwa9JkzZImM2tinovFLU9xAl/7aTTCWrmU9Vs4JDuV71kHcjwnngeJCKl4tIpp +HUB06Lk/5xnhYLKNpz087cjeSwXe5IBA2HBfXhFd+NH6+nVwwUUieq4A2n+8C/CK +zVJbH9iE8Lv99fpFyQwU/R63EzD8Hz9j4ny7oLnpb6QvFrVGr98jt/kJwlBb+0sR +RtIBnwMq4F7R5w5lgm6jzpZ5ibVuMeJh+k7Ulp7uu/rpcQKBwQDE3sWIvf7f7PaO +OpbJz0CmYjCHVLWrNIlGrPAv6Jid9U+cuXEkrCpGFl5V77CxIH59+bEuga0BMztl +ZkxP4khoqHhom6VpeWJ3nGGAFJRPYS0JJvTsYalilBPxSYdaoO+iZ6MdxpfozcE2 +m3KLW3uSEqlyYvpCqNJNWQhGEoeGXstADWyPevHPGgAhElwL/ZW8u9inU9Tc4sAI +BGnMer+BsaJ+ERU3lK+Clony+z2aZiFLfIUE93lM6DT2CZBN2QcCgcAVk4L0bfA6 +HFnP/ZWNlnYWpOVFKcq57PX+J5/k7Tf34e2cYM2P0eqYggWZbzVd8qoCOQCHrAx0 +aZSSvEyKAVvzRNeqbm1oXaMojksMnrSX5henHjPbZlr1EmM7+zMnSTMkfVOx/6g1 +97sASej31XdOAgKCBJGymrwvYrCLW+P5cHqd+D8v/PvfpRIQM54p5ixRt3EYZvtR +zGrzsr0OGyOLZtj1DB0a3kvajAAOCl3TawJSzviKo2mwc+/xj28MCQM= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIE4TCCA0mgAwIBAgIJALONCAWZxPhUMA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAk5aMQ4wDAYDVQQIDAVPdGFnbzEPMA0GA1UECgwGUGF0aG9kMREwDwYDVQQD +DAh0ZXN0LmNvbTAeFw0xNTA0MTgyMjA0NTNaFw00MjA5MDIyMjA0NTNaMEExCzAJ +BgNVBAYTAk5aMQ4wDAYDVQQIDAVPdGFnbzEPMA0GA1UECgwGUGF0aG9kMREwDwYD +VQQDDAh0ZXN0LmNvbTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAML7 +SsaGbwVdgMTwJAsfD127hOynqivWwb5PMfiXITzHX/ymY7Hbtr+hpvB4tOd4u2Rs +aLhb6JycRwIiunMJPTmEMGvX2zhX9poFihATuH2wh/d7Vgwk3VlcRcjVYgL8Rh5S +bN/yFNN6SBtvQIFJ4UVvKRm2mwH8fHpnDmSWd8pBLopmguLO5kcHXOtxl523I07U +ZKybAQEyPsbz8LkdvuHWl+7Dwn9JHpd2zdozKcF375P1sJCdgSBHmQoNKiL8jIx/ +aq1Iq1J7JkJ6T5Z9AhgW0BAbFvcjHxqBXURIjGJbOY2XqjcY1jb9UU0SJv+EYzMM +sZ6EwPXg0vcMDtjZyUuL31wJ1Ez+AWMfD3uGTANhpumKwsrE+mANcXRzaXLh1wpc +cJj4/3JvGeZCIOXJWzSCutLbPcha/wKNyjz2Cdhgb/PzngKTwSbrm3sLPv/f9dxM +P0idbdrtvPrD24F1BvHQtp+L1k7ycCn+g7qsYJ5vNs3c40maF0ULD/fOKojezQID +AQABo4HbMIHYMAsGA1UdDwQEAwIFoDAdBgNVHQ4EFgQUbEgfTauEqEP/bnBtby1K +bihJvcswcQYDVR0jBGowaIAUbEgfTauEqEP/bnBtby1KbihJvcuhRaRDMEExCzAJ +BgNVBAYTAk5aMQ4wDAYDVQQIDAVPdGFnbzEPMA0GA1UECgwGUGF0aG9kMREwDwYD +VQQDDAh0ZXN0LmNvbYIJALONCAWZxPhUMAwGA1UdEwQFMAMBAf8wKQYDVR0RBCIw +IIIIdGVzdC5jb22CCXRlc3QyLmNvbYIJdGVzdDMuY29tMA0GCSqGSIb3DQEBCwUA +A4IBgQBcTedXtUb91DxQRtg73iomz7cQ4niZntUBW8iE5rpoA7prtQNGHMCbHwaX +tbWFkzBmL5JTBWvd/6AQ2LtiB3rYB3W/iRhbpsNJ501xaoOguPEQ9720Ph8TEveM +208gNzGsEOcNALwyXj2y9M19NGu9zMa8eu1Tc3IsQaVaGKHx8XZn5HTNUx8EdcwI +Z/Ji9ETDCL7+e5INv0tqfFSazWaQUwxM4IzPMkKTYRcMuN/6eog609k9r9pp32Ut +rKlzc6GIkAlgJJ0Wkoz1V46DmJNJdJG7eLu/mtsB85j6hytIQeWTf1fll5YnMZLF +HgNZtfYn8Q0oTdBQ0ZOaZeQCfZ8emYBdLJf2YB83uGRMjQ1FoeIxzQqiRq8WHRdb +9Q45i0DINMnNp0DbLMA4numZ7wT9SQb6sql9eUyuCNDw7nGIWTHUNfLtU1Er3h1d +icJuApx9+//UN/pGh0yTXb3fZbiI4IehRmkpnIWonIAwaVGm6JZU04wiIn8CuBho +/qQdlS8= +-----END CERTIFICATE----- diff --git a/pathod/test/scripts/generate.sh b/pathod/test/scripts/generate.sh new file mode 100755 index 00000000..eec3077d --- /dev/null +++ b/pathod/test/scripts/generate.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +if [ ! -f ./private.key ] +then + openssl genrsa -out private.key 3072 +fi +openssl req \ + -batch \ + -new -x509 \ + -key private.key \ + -sha256 \ + -out cert.pem \ + -days 9999 \ + -config ./openssl.cnf +openssl x509 -in cert.pem -text -noout +cat ./private.key ./cert.pem > testcert.pem +rm ./private.key ./cert.pem diff --git a/pathod/test/scripts/openssl.cnf b/pathod/test/scripts/openssl.cnf new file mode 100644 index 00000000..5c890354 --- /dev/null +++ b/pathod/test/scripts/openssl.cnf @@ -0,0 +1,39 @@ +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +x509_extensions = v3_ca + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = NZ +countryName_min = 2 +countryName_max = 2 +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Otago +localityName = Locality Name (eg, city) +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Pathod +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = test.com +commonName_max = 64 + +[ v3_req ] + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + +keyUsage = digitalSignature, keyEncipherment +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always +basicConstraints = CA:true +subjectAltName = @alternate_names + + +[ alternate_names ] + +DNS.1 = test.com +DNS.2 = test2.com +DNS.3 = test3.com diff --git a/pathod/test/test_app.py b/pathod/test/test_app.py new file mode 100644 index 00000000..4536db8e --- /dev/null +++ b/pathod/test/test_app.py @@ -0,0 +1,85 @@ +import tutils + + +class TestApp(tutils.DaemonTests): + SSL = False + + def test_index(self): + r = self.getpath("/") + assert r.status_code == 200 + assert r.content + + def test_about(self): + r = self.getpath("/about") + assert r.ok + + def test_download(self): + r = self.getpath("/download") + assert r.ok + + def test_docs(self): + assert self.getpath("/docs/pathod").status_code == 200 + assert self.getpath("/docs/pathoc").status_code == 200 + assert self.getpath("/docs/language").status_code == 200 + assert self.getpath("/docs/libpathod").status_code == 200 + assert self.getpath("/docs/test").status_code == 200 + + def test_log(self): + assert self.getpath("/log").status_code == 200 + assert self.get("200:da").status_code == 200 + id = self.d.log()[0]["id"] + assert self.getpath("/log").status_code == 200 + assert self.getpath("/log/%s" % id).status_code == 200 + assert self.getpath("/log/9999999").status_code == 404 + + def test_log_binary(self): + assert self.get("200:h@10b=@10b:da") + + def test_response_preview(self): + r = self.getpath("/response_preview", params=dict(spec="200")) + assert r.status_code == 200 + assert 'Response' in r.content + + r = self.getpath("/response_preview", params=dict(spec="foo")) + assert r.status_code == 200 + assert 'Error' in r.content + + r = self.getpath("/response_preview", params=dict(spec="200:b@100m")) + assert r.status_code == 200 + assert "too large" in r.content + + r = self.getpath("/response_preview", params=dict(spec="200:b@5k")) + assert r.status_code == 200 + assert 'Response' in r.content + + r = self.getpath( + "/response_preview", + params=dict( + spec="200:b<nonexistent")) + assert r.status_code == 200 + assert 'File access denied' in r.content + + r = self.getpath("/response_preview", params=dict(spec="200:b<file")) + assert r.status_code == 200 + assert 'testfile' in r.content + + def test_request_preview(self): + r = self.getpath("/request_preview", params=dict(spec="get:/")) + assert r.status_code == 200 + assert 'Request' in r.content + + r = self.getpath("/request_preview", params=dict(spec="foo")) + assert r.status_code == 200 + assert 'Error' in r.content + + r = self.getpath("/request_preview", params=dict(spec="get:/:b@100m")) + assert r.status_code == 200 + assert "too large" in r.content + + r = self.getpath("/request_preview", params=dict(spec="get:/:b@5k")) + assert r.status_code == 200 + assert 'Request' in r.content + + r = self.getpath("/request_preview", params=dict(spec="")) + assert r.status_code == 200 + assert 'empty spec' in r.content diff --git a/pathod/test/test_language_actions.py b/pathod/test/test_language_actions.py new file mode 100644 index 00000000..755f0d85 --- /dev/null +++ b/pathod/test/test_language_actions.py @@ -0,0 +1,135 @@ +import cStringIO + +from libpathod.language import actions +from libpathod import language + + +def parse_request(s): + return language.parse_pathoc(s).next() + + +def test_unique_name(): + assert not actions.PauseAt(0, "f").unique_name + assert actions.DisconnectAt(0).unique_name + + +class TestDisconnects: + + def test_parse_pathod(self): + a = language.parse_pathod("400:d0").next().actions[0] + assert a.spec() == "d0" + a = language.parse_pathod("400:dr").next().actions[0] + assert a.spec() == "dr" + + def test_at(self): + e = actions.DisconnectAt.expr() + v = e.parseString("d0")[0] + assert isinstance(v, actions.DisconnectAt) + assert v.offset == 0 + + v = e.parseString("d100")[0] + assert v.offset == 100 + + e = actions.DisconnectAt.expr() + v = e.parseString("dr")[0] + assert v.offset == "r" + + def test_spec(self): + assert actions.DisconnectAt("r").spec() == "dr" + assert actions.DisconnectAt(10).spec() == "d10" + + +class TestInject: + + def test_parse_pathod(self): + a = language.parse_pathod("400:ir,@100").next().actions[0] + assert a.offset == "r" + assert a.value.datatype == "bytes" + assert a.value.usize == 100 + + a = language.parse_pathod("400:ia,@100").next().actions[0] + assert a.offset == "a" + + def test_at(self): + e = actions.InjectAt.expr() + v = e.parseString("i0,'foo'")[0] + assert v.value.val == "foo" + assert v.offset == 0 + assert isinstance(v, actions.InjectAt) + + v = e.parseString("ir,'foo'")[0] + assert v.offset == "r" + + def test_serve(self): + s = cStringIO.StringIO() + r = language.parse_pathod("400:i0,'foo'").next() + assert language.serve(r, s, {}) + + def test_spec(self): + e = actions.InjectAt.expr() + v = e.parseString("i0,'foo'")[0] + assert v.spec() == 'i0,"foo"' + + def test_spec(self): + e = actions.InjectAt.expr() + v = e.parseString("i0,@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + +class TestPauses: + + def test_parse_pathod(self): + e = actions.PauseAt.expr() + v = e.parseString("p10,10")[0] + assert v.seconds == 10 + assert v.offset == 10 + + v = e.parseString("p10,f")[0] + assert v.seconds == "f" + + v = e.parseString("pr,f")[0] + assert v.offset == "r" + + v = e.parseString("pa,f")[0] + assert v.offset == "a" + + def test_request(self): + r = language.parse_pathod('400:p10,10').next() + assert r.actions[0].spec() == "p10,10" + + def test_spec(self): + assert actions.PauseAt("r", 5).spec() == "pr,5" + assert actions.PauseAt(0, 5).spec() == "p0,5" + assert actions.PauseAt(0, "f").spec() == "p0,f" + + def test_freeze(self): + l = actions.PauseAt("r", 5) + assert l.freeze({}).spec() == l.spec() + + +class Test_Action: + + def test_cmp(self): + a = actions.DisconnectAt(0) + b = actions.DisconnectAt(1) + c = actions.DisconnectAt(0) + assert a < b + assert a == c + l = sorted([b, a]) + assert l[0].offset == 0 + + def test_resolve(self): + r = parse_request('GET:"/foo"') + e = actions.DisconnectAt("r") + ret = e.resolve({}, r) + assert isinstance(ret.offset, int) + + def test_repr(self): + e = actions.DisconnectAt("r") + assert repr(e) + + def test_freeze(self): + l = actions.DisconnectAt(5) + assert l.freeze({}).spec() == l.spec() diff --git a/pathod/test/test_language_base.py b/pathod/test/test_language_base.py new file mode 100644 index 00000000..b18ee5b2 --- /dev/null +++ b/pathod/test/test_language_base.py @@ -0,0 +1,352 @@ +import os +from libpathod import language +from libpathod.language import base, exceptions +import tutils + + +def parse_request(s): + return language.parse_pathoc(s).next() + + +def test_times(): + reqs = list(language.parse_pathoc("get:/:x5")) + assert len(reqs) == 5 + assert not reqs[0].times + + +def test_caseless_literal(): + class CL(base.CaselessLiteral): + TOK = "foo" + v = CL("foo") + assert v.expr() + assert v.values(language.Settings()) + + +class TestTokValueNakedLiteral: + + def test_expr(self): + v = base.TokValueNakedLiteral("foo") + assert v.expr() + + def test_spec(self): + v = base.TokValueNakedLiteral("foo") + assert v.spec() == repr(v) == "foo" + + v = base.TokValueNakedLiteral("f\x00oo") + assert v.spec() == repr(v) == r"f\x00oo" + + +class TestTokValueLiteral: + + def test_espr(self): + v = base.TokValueLiteral("foo") + assert v.expr() + assert v.val == "foo" + + v = base.TokValueLiteral("foo\n") + assert v.expr() + assert v.val == "foo\n" + assert repr(v) + + def test_spec(self): + v = base.TokValueLiteral("foo") + assert v.spec() == r"'foo'" + + v = base.TokValueLiteral("f\x00oo") + assert v.spec() == repr(v) == r"'f\x00oo'" + + v = base.TokValueLiteral("\"") + assert v.spec() == repr(v) == '\'"\'' + + def roundtrip(self, spec): + e = base.TokValueLiteral.expr() + v = base.TokValueLiteral(spec) + v2 = e.parseString(v.spec()) + assert v.val == v2[0].val + assert v.spec() == v2[0].spec() + + def test_roundtrip(self): + self.roundtrip("'") + self.roundtrip('\'') + self.roundtrip("a") + self.roundtrip("\"") + # self.roundtrip("\\") + self.roundtrip("200:b'foo':i23,'\\''") + self.roundtrip("\a") + + +class TestTokValueGenerate: + + def test_basic(self): + v = base.TokValue.parseString("@10b")[0] + assert v.usize == 10 + assert v.unit == "b" + assert v.bytes() == 10 + v = base.TokValue.parseString("@10")[0] + assert v.unit == "b" + v = base.TokValue.parseString("@10k")[0] + assert v.bytes() == 10240 + v = base.TokValue.parseString("@10g")[0] + assert v.bytes() == 1024 ** 3 * 10 + + v = base.TokValue.parseString("@10g,digits")[0] + assert v.datatype == "digits" + g = v.get_generator({}) + assert g[:100] + + v = base.TokValue.parseString("@10,digits")[0] + assert v.unit == "b" + assert v.datatype == "digits" + + def test_spec(self): + v = base.TokValueGenerate(1, "b", "bytes") + assert v.spec() == repr(v) == "@1" + + v = base.TokValueGenerate(1, "k", "bytes") + assert v.spec() == repr(v) == "@1k" + + v = base.TokValueGenerate(1, "k", "ascii") + assert v.spec() == repr(v) == "@1k,ascii" + + v = base.TokValueGenerate(1, "b", "ascii") + assert v.spec() == repr(v) == "@1,ascii" + + def test_freeze(self): + v = base.TokValueGenerate(100, "b", "ascii") + f = v.freeze(language.Settings()) + assert len(f.val) == 100 + + +class TestTokValueFile: + + def test_file_value(self): + v = base.TokValue.parseString("<'one two'")[0] + assert str(v) + assert v.path == "one two" + + v = base.TokValue.parseString("<path")[0] + assert v.path == "path" + + def test_access_control(self): + v = base.TokValue.parseString("<path")[0] + with tutils.tmpdir() as t: + p = os.path.join(t, "path") + with open(p, "wb") as f: + f.write("x" * 10000) + + assert v.get_generator(language.Settings(staticdir=t)) + + v = base.TokValue.parseString("<path2")[0] + tutils.raises( + exceptions.FileAccessDenied, + v.get_generator, + language.Settings(staticdir=t) + ) + tutils.raises( + "access disabled", + v.get_generator, + language.Settings() + ) + + v = base.TokValue.parseString("</outside")[0] + tutils.raises( + "outside", + v.get_generator, + language.Settings(staticdir=t) + ) + + def test_spec(self): + v = base.TokValue.parseString("<'one two'")[0] + v2 = base.TokValue.parseString(v.spec())[0] + assert v2.path == "one two" + + def test_freeze(self): + v = base.TokValue.parseString("<'one two'")[0] + v2 = v.freeze({}) + assert v2.path == v.path + + +class TestMisc: + + def test_generators(self): + v = base.TokValue.parseString("'val'")[0] + g = v.get_generator({}) + assert g[:] == "val" + + def test_value(self): + assert base.TokValue.parseString("'val'")[0].val == "val" + assert base.TokValue.parseString('"val"')[0].val == "val" + assert base.TokValue.parseString('"\'val\'"')[0].val == "'val'" + + def test_value(self): + class TT(base.Value): + preamble = "m" + e = TT.expr() + v = e.parseString("m'msg'")[0] + assert v.value.val == "msg" + + s = v.spec() + assert s == e.parseString(s)[0].spec() + + v = e.parseString("m@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + def test_fixedlengthvalue(self): + class TT(base.FixedLengthValue): + preamble = "m" + length = 4 + + e = TT.expr() + assert e.parseString("m@4") + tutils.raises("invalid value length", e.parseString, "m@100") + tutils.raises("invalid value length", e.parseString, "m@1") + + with tutils.tmpdir() as t: + p = os.path.join(t, "path") + s = base.Settings(staticdir=t) + with open(p, "wb") as f: + f.write("a" * 20) + v = e.parseString("m<path")[0] + tutils.raises("invalid value length", v.values, s) + + p = os.path.join(t, "path") + with open(p, "wb") as f: + f.write("a" * 4) + v = e.parseString("m<path")[0] + assert v.values(s) + + +class TKeyValue(base.KeyValue): + preamble = "h" + + def values(self, settings): + return [ + self.key.get_generator(settings), + ": ", + self.value.get_generator(settings), + "\r\n", + ] + + +class TestKeyValue: + + def test_simple(self): + e = TKeyValue.expr() + v = e.parseString("h'foo'='bar'")[0] + assert v.key.val == "foo" + assert v.value.val == "bar" + + v2 = e.parseString(v.spec())[0] + assert v2.key.val == v.key.val + assert v2.value.val == v.value.val + + s = v.spec() + assert s == e.parseString(s)[0].spec() + + def test_freeze(self): + e = TKeyValue.expr() + v = e.parseString("h@10=@10'")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.key.val == v3.key.val + assert v2.value.val == v3.value.val + + +def test_intfield(): + class TT(base.IntField): + preamble = "t" + names = { + "one": 1, + "two": 2, + "three": 3 + } + max = 4 + e = TT.expr() + + v = e.parseString("tone")[0] + assert v.value == 1 + assert v.spec() == "tone" + assert v.values(language.Settings()) + + v = e.parseString("t1")[0] + assert v.value == 1 + assert v.spec() == "t1" + + v = e.parseString("t4")[0] + assert v.value == 4 + assert v.spec() == "t4" + + tutils.raises("can't exceed", e.parseString, "t5") + + +def test_options_or_value(): + class TT(base.OptionsOrValue): + options = [ + "one", + "two", + "three" + ] + e = TT.expr() + assert e.parseString("one")[0].value.val == "one" + assert e.parseString("'foo'")[0].value.val == "foo" + assert e.parseString("'get'")[0].value.val == "get" + + assert e.parseString("one")[0].spec() == "one" + assert e.parseString("'foo'")[0].spec() == "'foo'" + + s = e.parseString("one")[0].spec() + assert s == e.parseString(s)[0].spec() + + s = e.parseString("'foo'")[0].spec() + assert s == e.parseString(s)[0].spec() + + v = e.parseString("@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + +def test_integer(): + e = base.Integer.expr() + v = e.parseString("200")[0] + assert v.string() == "200" + assert v.spec() == "200" + + assert v.freeze({}).value == v.value + + class BInt(base.Integer): + bounds = (1, 5) + + tutils.raises("must be between", BInt, 0) + tutils.raises("must be between", BInt, 6) + assert BInt(5) + assert BInt(1) + assert BInt(3) + + +class TBoolean(base.Boolean): + name = "test" + + +def test_unique_name(): + b = TBoolean(True) + assert b.unique_name + + +class test_boolean(): + e = TBoolean.expr() + assert e.parseString("test")[0].value + assert not e.parseString("-test")[0].value + + def roundtrip(s): + e = TBoolean.expr() + s2 = e.parseString(s)[0].spec() + v1 = e.parseString(s)[0].value + v2 = e.parseString(s2)[0].value + assert s == s2 + assert v1 == v2 + + roundtrip("test") + roundtrip("-test") diff --git a/pathod/test/test_language_generators.py b/pathod/test/test_language_generators.py new file mode 100644 index 00000000..945560c3 --- /dev/null +++ b/pathod/test/test_language_generators.py @@ -0,0 +1,42 @@ +import os + +from libpathod.language import generators +import tutils + + +def test_randomgenerator(): + g = generators.RandomGenerator("bytes", 100) + assert repr(g) + assert len(g[:10]) == 10 + assert len(g[1:10]) == 9 + assert len(g[:1000]) == 100 + assert len(g[1000:1001]) == 0 + assert g[0] + + +def test_filegenerator(): + with tutils.tmpdir() as t: + path = os.path.join(t, "foo") + f = open(path, "wb") + f.write("x" * 10000) + f.close() + g = generators.FileGenerator(path) + assert len(g) == 10000 + assert g[0] == "x" + assert g[-1] == "x" + assert g[0:5] == "xxxxx" + assert repr(g) + # remove all references to FileGenerator instance to close the file + # handle. + del g + + +def test_transform_generator(): + def trans(offset, data): + return "a" * len(data) + g = "one" + t = generators.TransformGenerator(g, trans) + assert len(t) == len(g) + assert t[0] == "a" + assert t[:] == "a" * len(g) + assert repr(t) diff --git a/pathod/test/test_language_http.py b/pathod/test/test_language_http.py new file mode 100644 index 00000000..26bb6a45 --- /dev/null +++ b/pathod/test/test_language_http.py @@ -0,0 +1,358 @@ +import cStringIO + +from libpathod import language +from libpathod.language import http, base +import tutils + + +def parse_request(s): + return language.parse_pathoc(s).next() + + +def test_make_error_response(): + d = cStringIO.StringIO() + s = http.make_error_response("foo") + language.serve(s, d, {}) + + +class TestRequest: + + def test_nonascii(self): + tutils.raises("ascii", parse_request, "get:\xf0") + + def test_err(self): + tutils.raises(language.ParseException, parse_request, 'GET') + + def test_simple(self): + r = parse_request('GET:"/foo"') + assert r.method.string() == "GET" + assert r.path.string() == "/foo" + r = parse_request('GET:/foo') + assert r.path.string() == "/foo" + r = parse_request('GET:@1k') + assert len(r.path.string()) == 1024 + + def test_multiple(self): + r = list(language.parse_pathoc("GET:/ PUT:/")) + assert r[0].method.string() == "GET" + assert r[1].method.string() == "PUT" + assert len(r) == 2 + + l = """ + GET + "/foo" + ir,@1 + + PUT + + "/foo + + + + bar" + + ir,@1 + """ + r = list(language.parse_pathoc(l)) + assert len(r) == 2 + assert r[0].method.string() == "GET" + assert r[1].method.string() == "PUT" + + l = """ + get:"http://localhost:9999/p/200":ir,@1 + get:"http://localhost:9999/p/200":ir,@2 + """ + r = list(language.parse_pathoc(l)) + assert len(r) == 2 + assert r[0].method.string() == "GET" + assert r[1].method.string() == "GET" + + def test_nested_response(self): + l = "get:/p:s'200'" + r = list(language.parse_pathoc(l)) + assert len(r) == 1 + assert len(r[0].tokens) == 3 + assert isinstance(r[0].tokens[2], http.NestedResponse) + assert r[0].values({}) + + def test_render(self): + s = cStringIO.StringIO() + r = parse_request("GET:'/foo'") + assert language.serve( + r, + s, + language.Settings(request_host="foo.com") + ) + + def test_multiline(self): + l = """ + GET + "/foo" + ir,@1 + """ + r = parse_request(l) + assert r.method.string() == "GET" + assert r.path.string() == "/foo" + assert r.actions + + l = """ + GET + + "/foo + + + + bar" + + ir,@1 + """ + r = parse_request(l) + assert r.method.string() == "GET" + assert r.path.string().endswith("bar") + assert r.actions + + def test_spec(self): + def rt(s): + s = parse_request(s).spec() + assert parse_request(s).spec() == s + rt("get:/foo") + rt("get:/foo:da") + + def test_freeze(self): + r = parse_request("GET:/:b@100").freeze(language.Settings()) + assert len(r.spec()) > 100 + + def test_path_generator(self): + r = parse_request("GET:@100").freeze(language.Settings()) + assert len(r.spec()) > 100 + + def test_websocket(self): + r = parse_request('ws:/path/') + res = r.resolve(language.Settings()) + assert res.method.string().lower() == "get" + assert res.tok(http.Path).value.val == "/path/" + assert res.tok(http.Method).value.val.lower() == "get" + assert http.get_header("Upgrade", res.headers).value.val == "websocket" + + r = parse_request('ws:put:/path/') + res = r.resolve(language.Settings()) + assert r.method.string().lower() == "put" + assert res.tok(http.Path).value.val == "/path/" + assert res.tok(http.Method).value.val.lower() == "put" + assert http.get_header("Upgrade", res.headers).value.val == "websocket" + + +class TestResponse: + + def dummy_response(self): + return language.parse_pathod("400'msg'").next() + + def test_response(self): + r = language.parse_pathod("400:m'msg'").next() + assert r.status_code.string() == "400" + assert r.reason.string() == "msg" + + r = language.parse_pathod("400:m'msg':b@100b").next() + assert r.reason.string() == "msg" + assert r.body.values({}) + assert str(r) + + r = language.parse_pathod("200").next() + assert r.status_code.string() == "200" + assert not r.reason + assert "OK" in [i[:] for i in r.preamble({})] + + def test_render(self): + s = cStringIO.StringIO() + r = language.parse_pathod("400:m'msg'").next() + assert language.serve(r, s, {}) + + r = language.parse_pathod("400:p0,100:dr").next() + assert "p0" in r.spec() + s = r.preview_safe() + assert "p0" not in s.spec() + + def test_raw(self): + s = cStringIO.StringIO() + r = language.parse_pathod("400:b'foo'").next() + language.serve(r, s, {}) + v = s.getvalue() + assert "Content-Length" in v + + s = cStringIO.StringIO() + r = language.parse_pathod("400:b'foo':r").next() + language.serve(r, s, {}) + v = s.getvalue() + assert "Content-Length" not in v + + def test_length(self): + def testlen(x): + s = cStringIO.StringIO() + x = x.next() + language.serve(x, s, language.Settings()) + assert x.length(language.Settings()) == len(s.getvalue()) + testlen(language.parse_pathod("400:m'msg':r")) + testlen(language.parse_pathod("400:m'msg':h'foo'='bar':r")) + testlen(language.parse_pathod("400:m'msg':h'foo'='bar':b@100b:r")) + + def test_maximum_length(self): + def testlen(x): + x = x.next() + s = cStringIO.StringIO() + m = x.maximum_length({}) + language.serve(x, s, {}) + assert m >= len(s.getvalue()) + + r = language.parse_pathod("400:m'msg':b@100:d0") + testlen(r) + + r = language.parse_pathod("400:m'msg':b@100:d0:i0,'foo'") + testlen(r) + + r = language.parse_pathod("400:m'msg':b@100:d0:i0,'foo'") + testlen(r) + + def test_parse_err(self): + tutils.raises( + language.ParseException, language.parse_pathod, "400:msg,b:" + ) + try: + language.parse_pathod("400'msg':b:") + except language.ParseException as v: + assert v.marked() + assert str(v) + + def test_nonascii(self): + tutils.raises("ascii", language.parse_pathod, "foo:b\xf0") + + def test_parse_header(self): + r = language.parse_pathod('400:h"foo"="bar"').next() + assert http.get_header("foo", r.headers) + + def test_parse_pause_before(self): + r = language.parse_pathod("400:p0,10").next() + assert r.actions[0].spec() == "p0,10" + + def test_parse_pause_after(self): + r = language.parse_pathod("400:pa,10").next() + assert r.actions[0].spec() == "pa,10" + + def test_parse_pause_random(self): + r = language.parse_pathod("400:pr,10").next() + assert r.actions[0].spec() == "pr,10" + + def test_parse_stress(self): + # While larger values are known to work on linux, len() technically + # returns an int and a python 2.7 int on windows has 32bit precision. + # Therefore, we should keep the body length < 2147483647 bytes in our + # tests. + r = language.parse_pathod("400:b@1g").next() + assert r.length({}) + + def test_spec(self): + def rt(s): + s = language.parse_pathod(s).next().spec() + assert language.parse_pathod(s).next().spec() == s + rt("400:b@100g") + rt("400") + rt("400:da") + + def test_websockets(self): + r = language.parse_pathod("ws").next() + tutils.raises("no websocket key", r.resolve, language.Settings()) + res = r.resolve(language.Settings(websocket_key="foo")) + assert res.status_code.string() == "101" + + +def test_ctype_shortcut(): + e = http.ShortcutContentType.expr() + v = e.parseString("c'foo'")[0] + assert v.key.val == "Content-Type" + assert v.value.val == "foo" + + s = v.spec() + assert s == e.parseString(s)[0].spec() + + e = http.ShortcutContentType.expr() + v = e.parseString("c@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + +def test_location_shortcut(): + e = http.ShortcutLocation.expr() + v = e.parseString("l'foo'")[0] + assert v.key.val == "Location" + assert v.value.val == "foo" + + s = v.spec() + assert s == e.parseString(s)[0].spec() + + e = http.ShortcutLocation.expr() + v = e.parseString("l@100")[0] + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + +def test_shortcuts(): + assert language.parse_pathod( + "400:c'foo'").next().headers[0].key.val == "Content-Type" + assert language.parse_pathod( + "400:l'foo'").next().headers[0].key.val == "Location" + + assert "Android" in tutils.render(parse_request("get:/:ua")) + assert "User-Agent" in tutils.render(parse_request("get:/:ua")) + + +def test_user_agent(): + e = http.ShortcutUserAgent.expr() + v = e.parseString("ua")[0] + assert "Android" in v.string() + + e = http.ShortcutUserAgent.expr() + v = e.parseString("u'a'")[0] + assert "Android" not in v.string() + + v = e.parseString("u@100'")[0] + assert len(str(v.freeze({}).value)) > 100 + v2 = v.freeze({}) + v3 = v2.freeze({}) + assert v2.value.val == v3.value.val + + +def test_nested_response(): + e = http.NestedResponse.expr() + v = e.parseString("s'200'")[0] + assert v.value.val == "200" + tutils.raises( + language.ParseException, + e.parseString, + "s'foo'" + ) + + v = e.parseString('s"200:b@1"')[0] + assert "@1" in v.spec() + f = v.freeze({}) + assert "@1" not in f.spec() + + +def test_nested_response_freeze(): + e = http.NestedResponse( + base.TokValueLiteral( + "200:b'foo':i10,'\\x27'".encode( + "string_escape" + ) + ) + ) + assert e.freeze({}) + assert e.values({}) + + +def test_unique_components(): + tutils.raises( + "multiple body clauses", + language.parse_pathod, + "400:b@1:b@1" + ) diff --git a/pathod/test/test_language_http2.py b/pathod/test/test_language_http2.py new file mode 100644 index 00000000..9be49452 --- /dev/null +++ b/pathod/test/test_language_http2.py @@ -0,0 +1,233 @@ +import cStringIO + +import netlib +from netlib import tcp +from netlib.http import user_agents + +from libpathod import language +from libpathod.language import http2, base +import tutils + + +def parse_request(s): + return language.parse_pathoc(s, True).next() + + +def parse_response(s): + return language.parse_pathod(s, True).next() + + +def default_settings(): + return language.Settings( + request_host="foo.com", + protocol=netlib.http.http2.HTTP2Protocol(tcp.TCPClient(('localhost', 1234))) + ) + + +def test_make_error_response(): + d = cStringIO.StringIO() + s = http2.make_error_response("foo", "bar") + language.serve(s, d, default_settings()) + + +class TestRequest: + + def test_cached_values(self): + req = parse_request("get:/") + req_id = id(req) + assert req_id == id(req.resolve(default_settings())) + assert req.values(default_settings()) == req.values(default_settings()) + + def test_nonascii(self): + tutils.raises("ascii", parse_request, "get:\xf0") + + def test_err(self): + tutils.raises(language.ParseException, parse_request, 'GET') + + def test_simple(self): + r = parse_request('GET:"/foo"') + assert r.method.string() == "GET" + assert r.path.string() == "/foo" + r = parse_request('GET:/foo') + assert r.path.string() == "/foo" + + def test_multiple(self): + r = list(language.parse_pathoc("GET:/ PUT:/")) + assert r[0].method.string() == "GET" + assert r[1].method.string() == "PUT" + assert len(r) == 2 + + l = """ + GET + "/foo" + + PUT + + "/foo + + + + bar" + """ + r = list(language.parse_pathoc(l, True)) + assert len(r) == 2 + assert r[0].method.string() == "GET" + assert r[1].method.string() == "PUT" + + l = """ + get:"http://localhost:9999/p/200" + get:"http://localhost:9999/p/200" + """ + r = list(language.parse_pathoc(l, True)) + assert len(r) == 2 + assert r[0].method.string() == "GET" + assert r[1].method.string() == "GET" + + def test_render_simple(self): + s = cStringIO.StringIO() + r = parse_request("GET:'/foo'") + assert language.serve( + r, + s, + default_settings(), + ) + + def test_raw_content_length(self): + r = parse_request('GET:/:r') + assert len(r.headers) == 0 + + r = parse_request('GET:/:r:b"foobar"') + assert len(r.headers) == 0 + + r = parse_request('GET:/') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-length", "0") + + r = parse_request('GET:/:b"foobar"') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-length", "6") + + r = parse_request('GET:/:b"foobar":h"content-length"="42"') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-length", "42") + + r = parse_request('GET:/:r:b"foobar":h"content-length"="42"') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-length", "42") + + def test_content_type(self): + r = parse_request('GET:/:r:c"foobar"') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-type", "foobar") + + def test_user_agent(self): + r = parse_request('GET:/:r:ua') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("user-agent", user_agents.get_by_shortcut('a')[2]) + + def test_render_with_headers(self): + s = cStringIO.StringIO() + r = parse_request('GET:/foo:h"foo"="bar"') + assert language.serve( + r, + s, + default_settings(), + ) + + def test_nested_response(self): + l = "get:/p/:s'200'" + r = parse_request(l) + assert len(r.tokens) == 3 + assert isinstance(r.tokens[2], http2.NestedResponse) + assert r.values(default_settings()) + + + def test_render_with_body(self): + s = cStringIO.StringIO() + r = parse_request("GET:'/foo':bfoobar") + assert language.serve( + r, + s, + default_settings(), + ) + + def test_spec(self): + def rt(s): + s = parse_request(s).spec() + assert parse_request(s).spec() == s + rt("get:/foo") + + +class TestResponse: + + def test_cached_values(self): + res = parse_response("200") + res_id = id(res) + assert res_id == id(res.resolve(default_settings())) + assert res.values(default_settings()) == res.values(default_settings()) + + def test_nonascii(self): + tutils.raises("ascii", parse_response, "200:\xf0") + + def test_err(self): + tutils.raises(language.ParseException, parse_response, 'GET:/') + + def test_raw_content_length(self): + r = parse_response('200:r') + assert len(r.headers) == 0 + + r = parse_response('200') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-length", "0") + + def test_content_type(self): + r = parse_response('200:r:c"foobar"') + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("content-type", "foobar") + + def test_simple(self): + r = parse_response('200:r:h"foo"="bar"') + assert r.status_code.string() == "200" + assert len(r.headers) == 1 + assert r.headers[0].values(default_settings()) == ("foo", "bar") + assert r.body is None + + r = parse_response('200:r:h"foo"="bar":bfoobar:h"bla"="fasel"') + assert r.status_code.string() == "200" + assert len(r.headers) == 2 + assert r.headers[0].values(default_settings()) == ("foo", "bar") + assert r.headers[1].values(default_settings()) == ("bla", "fasel") + assert r.body.string() == "foobar" + + def test_render_simple(self): + s = cStringIO.StringIO() + r = parse_response('200') + assert language.serve( + r, + s, + default_settings(), + ) + + def test_render_with_headers(self): + s = cStringIO.StringIO() + r = parse_response('200:h"foo"="bar"') + assert language.serve( + r, + s, + default_settings(), + ) + + def test_render_with_body(self): + s = cStringIO.StringIO() + r = parse_response('200:bfoobar') + assert language.serve( + r, + s, + default_settings(), + ) + + def test_spec(self): + def rt(s): + s = parse_response(s).spec() + assert parse_response(s).spec() == s + rt("200:bfoobar") diff --git a/pathod/test/test_language_websocket.py b/pathod/test/test_language_websocket.py new file mode 100644 index 00000000..d98fd33e --- /dev/null +++ b/pathod/test/test_language_websocket.py @@ -0,0 +1,142 @@ + +from libpathod import language +from libpathod.language import websockets +import netlib.websockets +import tutils + + +def parse_request(s): + return language.parse_pathoc(s).next() + + +class TestWebsocketFrame: + + def _test_messages(self, specs, message_klass): + for i in specs: + wf = parse_request(i) + assert isinstance(wf, message_klass) + assert wf + assert wf.values(language.Settings()) + assert wf.resolve(language.Settings()) + + spec = wf.spec() + wf2 = parse_request(spec) + assert wf2.spec() == spec + + def test_server_values(self): + specs = [ + "wf", + "wf:dr", + "wf:b'foo'", + "wf:mask:r'foo'", + "wf:l1024:b'foo'", + "wf:cbinary", + "wf:c1", + "wf:mask:knone", + "wf:fin", + "wf:fin:rsv1:rsv2:rsv3:mask", + "wf:-fin:-rsv1:-rsv2:-rsv3:-mask", + "wf:k@4", + "wf:x10", + ] + self._test_messages(specs, websockets.WebsocketFrame) + + def test_parse_websocket_frames(self): + wf = language.parse_websocket_frame("wf:x10") + assert len(list(wf)) == 10 + tutils.raises( + language.ParseException, + language.parse_websocket_frame, + "wf:x" + ) + + def test_client_values(self): + specs = [ + "wf:f'wf'", + ] + self._test_messages(specs, websockets.WebsocketClientFrame) + + def test_nested_frame(self): + wf = parse_request("wf:f'wf'") + assert wf.nested_frame + + def test_flags(self): + wf = parse_request("wf:fin:mask:rsv1:rsv2:rsv3") + frm = netlib.websockets.Frame.from_bytes(tutils.render(wf)) + assert frm.header.fin + assert frm.header.mask + assert frm.header.rsv1 + assert frm.header.rsv2 + assert frm.header.rsv3 + + wf = parse_request("wf:-fin:-mask:-rsv1:-rsv2:-rsv3") + frm = netlib.websockets.Frame.from_bytes(tutils.render(wf)) + assert not frm.header.fin + assert not frm.header.mask + assert not frm.header.rsv1 + assert not frm.header.rsv2 + assert not frm.header.rsv3 + + def fr(self, spec, **kwargs): + settings = language.base.Settings(**kwargs) + wf = parse_request(spec) + return netlib.websockets.Frame.from_bytes(tutils.render(wf, settings)) + + def test_construction(self): + assert self.fr("wf:c1").header.opcode == 1 + assert self.fr("wf:c0").header.opcode == 0 + assert self.fr("wf:cbinary").header.opcode ==\ + netlib.websockets.OPCODE.BINARY + assert self.fr("wf:ctext").header.opcode ==\ + netlib.websockets.OPCODE.TEXT + + def test_rawbody(self): + frm = self.fr("wf:mask:r'foo'") + assert len(frm.payload) == 3 + assert frm.payload != "foo" + + assert self.fr("wf:r'foo'").payload == "foo" + + def test_construction(self): + # Simple server frame + frm = self.fr("wf:b'foo'") + assert not frm.header.mask + assert not frm.header.masking_key + + # Simple client frame + frm = self.fr("wf:b'foo'", is_client=True) + assert frm.header.mask + assert frm.header.masking_key + frm = self.fr("wf:b'foo':k'abcd'", is_client=True) + assert frm.header.mask + assert frm.header.masking_key == 'abcd' + + # Server frame, mask explicitly set + frm = self.fr("wf:b'foo':mask") + assert frm.header.mask + assert frm.header.masking_key + frm = self.fr("wf:b'foo':k'abcd'") + assert frm.header.mask + assert frm.header.masking_key == 'abcd' + + # Client frame, mask explicitly unset + frm = self.fr("wf:b'foo':-mask", is_client=True) + assert not frm.header.mask + assert not frm.header.masking_key + + frm = self.fr("wf:b'foo':-mask:k'abcd'", is_client=True) + assert not frm.header.mask + # We're reading back a corrupted frame - the first 3 characters of the + # mask is mis-interpreted as the payload + assert frm.payload == "abc" + + def test_knone(self): + with tutils.raises("expected 4 bytes"): + self.fr("wf:b'foo':mask:knone") + + def test_length(self): + assert self.fr("wf:l3:b'foo'").header.payload_length == 3 + frm = self.fr("wf:l2:b'foo'") + assert frm.header.payload_length == 2 + assert frm.payload == "fo" + tutils.raises("expected 1024 bytes", self.fr, "wf:l1024:b'foo'") diff --git a/pathod/test/test_language_writer.py b/pathod/test/test_language_writer.py new file mode 100644 index 00000000..1a532903 --- /dev/null +++ b/pathod/test/test_language_writer.py @@ -0,0 +1,91 @@ +import cStringIO + +from libpathod import language +from libpathod.language import writer + + +def test_send_chunk(): + v = "foobarfoobar" + for bs in range(1, len(v) + 2): + s = cStringIO.StringIO() + writer.send_chunk(s, v, bs, 0, len(v)) + assert s.getvalue() == v + for start in range(len(v)): + for end in range(len(v)): + s = cStringIO.StringIO() + writer.send_chunk(s, v, bs, start, end) + assert s.getvalue() == v[start:end] + + +def test_write_values_inject(): + tst = "foo" + + s = cStringIO.StringIO() + writer.write_values(s, [tst], [(0, "inject", "aaa")], blocksize=5) + assert s.getvalue() == "aaafoo" + + s = cStringIO.StringIO() + writer.write_values(s, [tst], [(1, "inject", "aaa")], blocksize=5) + assert s.getvalue() == "faaaoo" + + s = cStringIO.StringIO() + writer.write_values(s, [tst], [(1, "inject", "aaa")], blocksize=5) + assert s.getvalue() == "faaaoo" + + +def test_write_values_disconnects(): + s = cStringIO.StringIO() + tst = "foo" * 100 + writer.write_values(s, [tst], [(0, "disconnect")], blocksize=5) + assert not s.getvalue() + + +def test_write_values(): + tst = "foobarvoing" + s = cStringIO.StringIO() + writer.write_values(s, [tst], []) + assert s.getvalue() == tst + + for bs in range(1, len(tst) + 2): + for off in range(len(tst)): + s = cStringIO.StringIO() + writer.write_values( + s, [tst], [(off, "disconnect")], blocksize=bs + ) + assert s.getvalue() == tst[:off] + + +def test_write_values_pauses(): + tst = "".join(str(i) for i in range(10)) + for i in range(2, 10): + s = cStringIO.StringIO() + writer.write_values( + s, [tst], [(2, "pause", 0), (1, "pause", 0)], blocksize=i + ) + assert s.getvalue() == tst + + for i in range(2, 10): + s = cStringIO.StringIO() + writer.write_values(s, [tst], [(1, "pause", 0)], blocksize=i) + assert s.getvalue() == tst + + tst = ["".join(str(i) for i in range(10))] * 5 + for i in range(2, 10): + s = cStringIO.StringIO() + writer.write_values(s, tst[:], [(1, "pause", 0)], blocksize=i) + assert s.getvalue() == "".join(tst) + + +def test_write_values_after(): + s = cStringIO.StringIO() + r = language.parse_pathod("400:da").next() + language.serve(r, s, {}) + + s = cStringIO.StringIO() + r = language.parse_pathod("400:pa,0").next() + language.serve(r, s, {}) + + s = cStringIO.StringIO() + r = language.parse_pathod("400:ia,'xx'").next() + language.serve(r, s, {}) + assert s.getvalue().endswith('xx') diff --git a/pathod/test/test_log.py b/pathod/test/test_log.py new file mode 100644 index 00000000..8f38c040 --- /dev/null +++ b/pathod/test/test_log.py @@ -0,0 +1,25 @@ +import StringIO +from libpathod import log +from netlib.exceptions import TcpDisconnect +import netlib.tcp + + +class DummyIO(StringIO.StringIO): + + def start_log(self, *args, **kwargs): + pass + + def get_log(self, *args, **kwargs): + return "" + + +def test_disconnect(): + outf = DummyIO() + rw = DummyIO() + l = log.ConnectionLogger(outf, False, rw, rw) + try: + with l.ctx() as lg: + lg("Test") + except TcpDisconnect: + pass + assert "Test" in outf.getvalue() diff --git a/pathod/test/test_pathoc.py b/pathod/test/test_pathoc.py new file mode 100644 index 00000000..62696a64 --- /dev/null +++ b/pathod/test/test_pathoc.py @@ -0,0 +1,308 @@ +import json +import cStringIO +import re +import OpenSSL +from mock import Mock + +from netlib import tcp, http, socks +from netlib.exceptions import HttpException, TcpException, NetlibException +from netlib.http import http1, http2 + +from libpathod import pathoc, test, version, pathod, language +from netlib.tutils import raises +import tutils + + +def test_response(): + r = http.Response("HTTP/1.1", 200, "Message", {}, None, None) + assert repr(r) + + +class _TestDaemon: + ssloptions = pathod.SSLOptions() + + @classmethod + def setup_class(cls): + cls.d = test.Daemon( + ssl=cls.ssl, + ssloptions=cls.ssloptions, + staticdir=tutils.test_data.path("data"), + anchors=[ + (re.compile("/anchor/.*"), "202") + ] + ) + + @classmethod + def teardown_class(cls): + cls.d.shutdown() + + def setUp(self): + self.d.clear_log() + + def test_info(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + ssl=self.ssl, + fp=None + ) + c.connect() + resp = c.request("get:/api/info") + assert tuple(json.loads(resp.content)["version"]) == version.IVERSION + + def tval( + self, + requests, + showreq=False, + showresp=False, + explain=False, + showssl=False, + hexdump=False, + timeout=None, + ignorecodes=(), + ignoretimeout=None, + showsummary=True + ): + s = cStringIO.StringIO() + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + ssl=self.ssl, + showreq=showreq, + showresp=showresp, + explain=explain, + hexdump=hexdump, + ignorecodes=ignorecodes, + ignoretimeout=ignoretimeout, + showsummary=showsummary, + fp=s + ) + c.connect(showssl=showssl, fp=s) + if timeout: + c.settimeout(timeout) + for i in requests: + r = language.parse_pathoc(i).next() + if explain: + r = r.freeze(language.Settings()) + try: + c.request(r) + except NetlibException: + pass + return s.getvalue() + + +class TestDaemonSSL(_TestDaemon): + ssl = True + ssloptions = pathod.SSLOptions( + request_client_cert=True, + sans=["test1.com", "test2.com"], + alpn_select=b'h2', + ) + + def test_sni(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + ssl=True, + sni="foobar.com", + fp=None + ) + c.connect() + c.request("get:/p/200") + r = c.request("get:/api/log") + d = json.loads(r.content) + assert d["log"][0]["request"]["sni"] == "foobar.com" + + def test_showssl(self): + assert "certificate chain" in self.tval(["get:/p/200"], showssl=True) + + def test_clientcert(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + ssl=True, + clientcert=tutils.test_data.path("data/clientcert/client.pem"), + fp=None + ) + c.connect() + c.request("get:/p/200") + r = c.request("get:/api/log") + d = json.loads(r.content) + assert d["log"][0]["request"]["clientcert"]["keyinfo"] + + def test_http2_without_ssl(self): + fp = cStringIO.StringIO() + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + use_http2=True, + ssl=False, + fp = fp + ) + tutils.raises(NotImplementedError, c.connect) + + +class TestDaemon(_TestDaemon): + ssl = False + + def test_ssl_error(self): + c = pathoc.Pathoc(("127.0.0.1", self.d.port), ssl=True, fp=None) + tutils.raises("ssl handshake", c.connect) + + def test_showssl(self): + assert not "certificate chain" in self.tval( + ["get:/p/200"], + showssl=True) + + def test_ignorecodes(self): + assert "200" in self.tval(["get:'/p/200:b@1'"]) + assert "200" in self.tval(["get:'/p/200:b@1'"]) + assert "200" in self.tval(["get:'/p/200:b@1'"]) + assert "200" not in self.tval(["get:'/p/200:b@1'"], ignorecodes=[200]) + assert "200" not in self.tval( + ["get:'/p/200:b@1'"], + ignorecodes=[ + 200, + 201]) + assert "202" in self.tval(["get:'/p/202:b@1'"], ignorecodes=[200, 201]) + + def test_timeout(self): + assert "Timeout" in self.tval(["get:'/p/200:p0,100'"], timeout=0.01) + assert "HTTP" in self.tval( + ["get:'/p/200:p5,100'"], + showresp=True, + timeout=1 + ) + assert not "HTTP" in self.tval( + ["get:'/p/200:p3,100'"], + showresp=True, + timeout=1, + ignoretimeout=True + ) + + def test_showresp(self): + reqs = ["get:/api/info:p0,0", "get:/api/info:p0,0"] + assert self.tval(reqs).count("200") == 2 + assert self.tval(reqs, showresp=True).count("HTTP/1.1 200 OK") == 2 + assert self.tval( + reqs, showresp=True, hexdump=True + ).count("0000000000") == 2 + + def test_showresp_httperr(self): + v = self.tval(["get:'/p/200:d20'"], showresp=True, showsummary=True) + assert "Invalid headers" in v + assert "HTTP/" in v + + def test_explain(self): + reqs = ["get:/p/200:b@100"] + assert "b@100" not in self.tval(reqs, explain=True) + + def test_showreq(self): + reqs = ["get:/api/info:p0,0", "get:/api/info:p0,0"] + assert self.tval(reqs, showreq=True).count("GET /api") == 2 + assert self.tval( + reqs, showreq=True, hexdump=True + ).count("0000000000") == 2 + + def test_conn_err(self): + assert "Invalid server response" in self.tval(["get:'/p/200:d2'"]) + + def test_websocket_shutdown(self): + c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) + c.connect() + c.request("ws:/") + c.stop() + + def test_wait_finish(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + fp=None, + ws_read_limit=1 + ) + c.connect() + c.request("ws:/") + c.request("wf:f'wf:x100'") + [i for i in c.wait(timeout=0, finish=False)] + [i for i in c.wait(timeout=0)] + + def test_connect_fail(self): + to = ("foobar", 80) + c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) + c.rfile, c.wfile = cStringIO.StringIO(), cStringIO.StringIO() + with raises("connect failed"): + c.http_connect(to) + c.rfile = cStringIO.StringIO( + "HTTP/1.1 500 OK\r\n" + ) + with raises("connect failed"): + c.http_connect(to) + c.rfile = cStringIO.StringIO( + "HTTP/1.1 200 OK\r\n" + ) + c.http_connect(to) + + def test_socks_connect(self): + to = ("foobar", 80) + c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) + c.rfile, c.wfile = tutils.treader(""), cStringIO.StringIO() + tutils.raises(pathoc.PathocError, c.socks_connect, to) + + c.rfile = tutils.treader( + "\x05\xEE" + ) + tutils.raises("SOCKS without authentication", c.socks_connect, ("example.com", 0xDEAD)) + + c.rfile = tutils.treader( + "\x05\x00" + + "\x05\xEE\x00\x03\x0bexample.com\xDE\xAD" + ) + tutils.raises("SOCKS server error", c.socks_connect, ("example.com", 0xDEAD)) + + c.rfile = tutils.treader( + "\x05\x00" + + "\x05\x00\x00\x03\x0bexample.com\xDE\xAD" + ) + c.socks_connect(("example.com", 0xDEAD)) + + +class TestDaemonHTTP2(_TestDaemon): + ssl = True + + if OpenSSL._util.lib.Cryptography_HAS_ALPN: + + def test_http2(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + fp=None, + ssl=True, + use_http2=True, + ) + assert isinstance(c.protocol, http2.HTTP2Protocol) + + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + ) + assert c.protocol == http1 + + def test_http2_alpn(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + fp=None, + ssl=True, + use_http2=True, + http2_skip_connection_preface=True, + ) + + tmp_convert_to_ssl = c.convert_to_ssl + c.convert_to_ssl = Mock() + c.convert_to_ssl.side_effect = tmp_convert_to_ssl + c.connect() + + _, kwargs = c.convert_to_ssl.call_args + assert set(kwargs['alpn_protos']) == set([b'http/1.1', b'h2']) + + def test_request(self): + c = pathoc.Pathoc( + ("127.0.0.1", self.d.port), + fp=None, + ssl=True, + use_http2=True, + ) + c.connect() + resp = c.request("get:/p/200") + assert resp.status_code == 200 diff --git a/pathod/test/test_pathoc_cmdline.py b/pathod/test/test_pathoc_cmdline.py new file mode 100644 index 00000000..74dfef57 --- /dev/null +++ b/pathod/test/test_pathoc_cmdline.py @@ -0,0 +1,59 @@ +from libpathod import pathoc_cmdline as cmdline +import tutils +import cStringIO +import mock + + +@mock.patch("argparse.ArgumentParser.error") +def test_pathoc(perror): + assert cmdline.args_pathoc(["pathoc", "foo.com", "get:/"]) + s = cStringIO.StringIO() + with tutils.raises(SystemExit): + cmdline.args_pathoc(["pathoc", "--show-uas"], s, s) + + a = cmdline.args_pathoc(["pathoc", "foo.com:8888", "get:/"]) + assert a.port == 8888 + + a = cmdline.args_pathoc(["pathoc", "foo.com:xxx", "get:/"]) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathoc(["pathoc", "-I", "10, 20", "foo.com:8888", "get:/"]) + assert a.ignorecodes == [10, 20] + + a = cmdline.args_pathoc(["pathoc", "-I", "xx, 20", "foo.com:8888", "get:/"]) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathoc(["pathoc", "-c", "foo:10", "foo.com:8888", "get:/"]) + assert a.connect_to == ["foo", 10] + + a = cmdline.args_pathoc(["pathoc", "foo.com", "get:/", "--http2"]) + assert a.use_http2 == True + assert a.ssl == True + + a = cmdline.args_pathoc(["pathoc", "foo.com", "get:/", "--http2-skip-connection-preface"]) + assert a.use_http2 == True + assert a.ssl == True + assert a.http2_skip_connection_preface == True + + a = cmdline.args_pathoc(["pathoc", "-c", "foo", "foo.com:8888", "get:/"]) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathoc( + ["pathoc", "-c", "foo:bar", "foo.com:8888", "get:/"]) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathoc( + [ + "pathoc", + "foo.com:8888", + tutils.test_data.path("data/request") + ] + ) + assert len(list(a.requests)) == 1 + + with tutils.raises(SystemExit): + cmdline.args_pathoc(["pathoc", "foo.com", "invalid"], s, s) diff --git a/pathod/test/test_pathod.py b/pathod/test/test_pathod.py new file mode 100644 index 00000000..98da7d28 --- /dev/null +++ b/pathod/test/test_pathod.py @@ -0,0 +1,289 @@ +import sys +import cStringIO +import OpenSSL + +from libpathod import pathod, version +from netlib import tcp, http +from netlib.exceptions import HttpException, TlsException +import tutils + + +class TestPathod(object): + + def test_logging(self): + s = cStringIO.StringIO() + p = pathod.Pathod(("127.0.0.1", 0), logfp=s) + assert len(p.get_log()) == 0 + id = p.add_log(dict(s="foo")) + assert p.log_by_id(id) + assert len(p.get_log()) == 1 + p.clear_log() + assert len(p.get_log()) == 0 + + for _ in range(p.LOGBUF + 1): + p.add_log(dict(s="foo")) + assert len(p.get_log()) <= p.LOGBUF + + +class TestNoWeb(tutils.DaemonTests): + noweb = True + + def test_noweb(self): + assert self.get("200:da").status_code == 200 + assert self.getpath("/").status_code == 800 + + +class TestTimeout(tutils.DaemonTests): + timeout = 0.01 + + def test_noweb(self): + # FIXME: Add float values to spec language, reduce test timeout to + # increase test performance + # This is a bodge - we have some platform difference that causes + # different exceptions to be raised here. + tutils.raises(Exception, self.pathoc, ["get:/:p1,1"]) + assert self.d.last_log()["type"] == "timeout" + + +class TestNoApi(tutils.DaemonTests): + noapi = True + + def test_noapi(self): + assert self.getpath("/log").status_code == 404 + r = self.getpath("/") + assert r.status_code == 200 + assert not "Log" in r.content + + +class TestNotAfterConnect(tutils.DaemonTests): + ssl = False + ssloptions = dict( + not_after_connect=True + ) + + def test_connect(self): + r, _ = self.pathoc( + [r"get:'http://foo.com/p/202':da"], + connect_to=("localhost", self.d.port) + ) + assert r[0].status_code == 202 + + +class TestCustomCert(tutils.DaemonTests): + ssl = True + ssloptions = dict( + certs=[("*", tutils.test_data.path("data/testkey.pem"))], + ) + + def test_connect(self): + r, _ = self.pathoc([r"get:/p/202"]) + r = r[0] + assert r.status_code == 202 + assert r.sslinfo + assert "test.com" in str(r.sslinfo.certchain[0].get_subject()) + + +class TestSSLCN(tutils.DaemonTests): + ssl = True + ssloptions = dict( + cn="foo.com" + ) + + def test_connect(self): + r, _ = self.pathoc([r"get:/p/202"]) + r = r[0] + assert r.status_code == 202 + assert r.sslinfo + assert r.sslinfo.certchain[0].get_subject().CN == "foo.com" + + +class TestNohang(tutils.DaemonTests): + nohang = True + + def test_nohang(self): + r = self.get("200:p0,0") + assert r.status_code == 800 + l = self.d.last_log() + assert "Pauses have been disabled" in l["response"]["msg"] + + +class TestHexdump(tutils.DaemonTests): + hexdump = True + + def test_hexdump(self): + r = self.get(r"200:b'\xf0'") + + +class TestNocraft(tutils.DaemonTests): + nocraft = True + + def test_nocraft(self): + r = self.get(r"200:b'\xf0'") + assert r.status_code == 800 + assert "Crafting disabled" in r.content + + +class CommonTests(tutils.DaemonTests): + + def test_binarydata(self): + r = self.get(r"200:b'\xf0'") + l = self.d.last_log() + # FIXME: Other binary data elements + + def test_sizelimit(self): + r = self.get("200:b@1g") + assert r.status_code == 800 + l = self.d.last_log() + assert "too large" in l["response"]["msg"] + + def test_preline(self): + r, _ = self.pathoc([r"get:'/p/200':i0,'\r\n'"]) + assert r[0].status_code == 200 + + def test_info(self): + assert tuple(self.d.info()["version"]) == version.IVERSION + + def test_logs(self): + assert self.d.clear_log() + assert not self.d.last_log() + rsp = self.get("202:da") + assert len(self.d.log()) == 1 + assert self.d.clear_log() + assert len(self.d.log()) == 0 + + def test_disconnect(self): + rsp = self.get("202:b@100k:d200") + assert len(rsp.content) < 200 + + def test_parserr(self): + rsp = self.get("400:msg,b:") + assert rsp.status_code == 800 + + def test_static(self): + rsp = self.get("200:b<file") + assert rsp.status_code == 200 + assert rsp.content.strip() == "testfile" + + def test_anchor(self): + rsp = self.getpath("anchor/foo") + assert rsp.status_code == 202 + + def test_invalid_first_line(self): + c = tcp.TCPClient(("localhost", self.d.port)) + c.connect() + if self.ssl: + c.convert_to_ssl() + c.wfile.write("foo\n\n\n") + c.wfile.flush() + l = self.d.last_log() + assert l["type"] == "error" + assert "foo" in l["msg"] + + def test_invalid_content_length(self): + tutils.raises( + HttpException, + self.pathoc, + ["get:/:h'content-length'='foo'"] + ) + l = self.d.last_log() + assert l["type"] == "error" + assert "Unparseable Content Length" in l["msg"] + + def test_invalid_headers(self): + tutils.raises(HttpException, self.pathoc, ["get:/:h'\t'='foo'"]) + l = self.d.last_log() + assert l["type"] == "error" + assert "Invalid headers" in l["msg"] + + def test_access_denied(self): + rsp = self.get("=nonexistent") + assert rsp.status_code == 800 + + def test_source_access_denied(self): + rsp = self.get("200:b</foo") + assert rsp.status_code == 800 + assert "File access denied" in rsp.content + + def test_proxy(self): + r, _ = self.pathoc([r"get:'http://foo.com/p/202':da"]) + assert r[0].status_code == 202 + + def test_websocket(self): + r, _ = self.pathoc(["ws:/p/"], ws_read_limit=0) + assert r[0].status_code == 101 + + r, _ = self.pathoc(["ws:/p/ws"], ws_read_limit=0) + assert r[0].status_code == 101 + + def test_websocket_frame(self): + r, _ = self.pathoc( + ["ws:/p/", "wf:f'wf:b\"test\"':pa,1"], + ws_read_limit=1 + ) + assert r[1].payload == "test" + + def test_websocket_frame_reflect_error(self): + r, _ = self.pathoc( + ["ws:/p/", "wf:-mask:knone:f'wf:b@10':i13,'a'"], + ws_read_limit=1, + timeout=1 + ) + # FIXME: Race Condition? + assert "Parse error" in self.d.text_log() + + def test_websocket_frame_disconnect_error(self): + self.pathoc(["ws:/p/", "wf:b@10:d3"], ws_read_limit=0) + assert self.d.last_log() + + +class TestDaemon(CommonTests): + ssl = False + + def test_connect(self): + r, _ = self.pathoc( + [r"get:'http://foo.com/p/202':da"], + connect_to=("localhost", self.d.port), + ssl=True + ) + assert r[0].status_code == 202 + + def test_connect_err(self): + tutils.raises( + HttpException, + self.pathoc, + [r"get:'http://foo.com/p/202':da"], + connect_to=("localhost", self.d.port) + ) + + +class TestDaemonSSL(CommonTests): + ssl = True + + def test_ssl_conn_failure(self): + c = tcp.TCPClient(("localhost", self.d.port)) + c.rbufsize = 0 + c.wbufsize = 0 + c.connect() + c.wfile.write("\0\0\0\0") + tutils.raises(TlsException, c.convert_to_ssl) + l = self.d.last_log() + assert l["type"] == "error" + assert "SSL" in l["msg"] + + def test_ssl_cipher(self): + r, _ = self.pathoc([r"get:/p/202"]) + assert r[0].status_code == 202 + assert self.d.last_log()["cipher"][1] > 0 + + +class TestHTTP2(tutils.DaemonTests): + ssl = True + noweb = True + noapi = True + nohang = True + + if OpenSSL._util.lib.Cryptography_HAS_ALPN: + + def test_http2(self): + r, _ = self.pathoc(["GET:/"], ssl=True, use_http2=True) + assert r[0].status_code == 800 diff --git a/pathod/test/test_pathod_cmdline.py b/pathod/test/test_pathod_cmdline.py new file mode 100644 index 00000000..829c4b32 --- /dev/null +++ b/pathod/test/test_pathod_cmdline.py @@ -0,0 +1,85 @@ +from libpathod import pathod_cmdline as cmdline +import tutils +import cStringIO +import mock + + +@mock.patch("argparse.ArgumentParser.error") +def test_pathod(perror): + assert cmdline.args_pathod(["pathod"]) + + a = cmdline.args_pathod( + [ + "pathod", + "--cert", + tutils.test_data.path("data/testkey.pem") + ] + ) + assert a.ssl_certs + + a = cmdline.args_pathod( + [ + "pathod", + "--cert", + "nonexistent" + ] + ) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathod( + [ + "pathod", + "-a", + "foo=200" + ] + ) + assert a.anchors + + a = cmdline.args_pathod( + [ + "pathod", + "-a", + "foo=" + tutils.test_data.path("data/response") + ] + ) + assert a.anchors + + a = cmdline.args_pathod( + [ + "pathod", + "-a", + "?=200" + ] + ) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathod( + [ + "pathod", + "-a", + "foo" + ] + ) + assert perror.called + perror.reset_mock() + + a = cmdline.args_pathod( + [ + "pathod", + "--limit-size", + "200k" + ] + ) + assert a.sizelimit + + a = cmdline.args_pathod( + [ + "pathod", + "--limit-size", + "q" + ] + ) + assert perror.called + perror.reset_mock() diff --git a/pathod/test/test_test.py b/pathod/test/test_test.py new file mode 100644 index 00000000..bd92d864 --- /dev/null +++ b/pathod/test/test_test.py @@ -0,0 +1,45 @@ +import logging +import requests +from libpathod import test +import tutils +logging.disable(logging.CRITICAL) + + +class TestDaemonManual: + + def test_simple(self): + with test.Daemon() as d: + rsp = requests.get("http://localhost:%s/p/202:da" % d.port) + assert rsp.ok + assert rsp.status_code == 202 + with tutils.raises(requests.ConnectionError): + requests.get("http://localhost:%s/p/202:da" % d.port) + + def test_startstop_ssl(self): + d = test.Daemon(ssl=True) + rsp = requests.get( + "https://localhost:%s/p/202:da" % + d.port, + verify=False) + assert rsp.ok + assert rsp.status_code == 202 + d.shutdown() + with tutils.raises(requests.ConnectionError): + requests.get("http://localhost:%s/p/202:da" % d.port) + + def test_startstop_ssl_explicit(self): + ssloptions = dict( + certfile=tutils.test_data.path("data/testkey.pem"), + cacert=tutils.test_data.path("data/testkey.pem"), + ssl_after_connect=False + ) + d = test.Daemon(ssl=ssloptions) + rsp = requests.get( + "https://localhost:%s/p/202:da" % + d.port, + verify=False) + assert rsp.ok + assert rsp.status_code == 202 + d.shutdown() + with tutils.raises(requests.ConnectionError): + requests.get("http://localhost:%s/p/202:da" % d.port) diff --git a/pathod/test/test_utils.py b/pathod/test/test_utils.py new file mode 100644 index 00000000..7d24e9e4 --- /dev/null +++ b/pathod/test/test_utils.py @@ -0,0 +1,39 @@ +from libpathod import utils +import tutils + + +def test_membool(): + m = utils.MemBool() + assert not m.v + assert m(1) + assert m.v == 1 + assert m(2) + assert m.v == 2 + + +def test_parse_size(): + assert utils.parse_size("100") == 100 + assert utils.parse_size("100k") == 100 * 1024 + tutils.raises("invalid size spec", utils.parse_size, "foo") + tutils.raises("invalid size spec", utils.parse_size, "100kk") + + +def test_parse_anchor_spec(): + assert utils.parse_anchor_spec("foo=200") == ("foo", "200") + assert utils.parse_anchor_spec("foo") is None + + +def test_data_path(): + tutils.raises(ValueError, utils.data.path, "nonexistent") + + +def test_inner_repr(): + assert utils.inner_repr("\x66") == "\x66" + assert utils.inner_repr(u"foo") == "foo" + + +def test_escape_unprintables(): + s = "".join([chr(i) for i in range(255)]) + e = utils.escape_unprintables(s) + assert e.encode('ascii') + assert not "PATHOD_MARKER" in e diff --git a/pathod/test/tutils.py b/pathod/test/tutils.py new file mode 100644 index 00000000..664cdd52 --- /dev/null +++ b/pathod/test/tutils.py @@ -0,0 +1,128 @@ +import tempfile +import os +import re +import shutil +import cStringIO +from contextlib import contextmanager + +import netlib +from libpathod import utils, test, pathoc, pathod, language +from netlib import tcp +import requests + +def treader(bytes): + """ + Construct a tcp.Read object from bytes. + """ + fp = cStringIO.StringIO(bytes) + return tcp.Reader(fp) + + +class DaemonTests(object): + noweb = False + noapi = False + nohang = False + ssl = False + timeout = None + hexdump = False + ssloptions = None + nocraft = False + + @classmethod + def setup_class(cls): + opts = cls.ssloptions or {} + cls.confdir = tempfile.mkdtemp() + opts["confdir"] = cls.confdir + so = pathod.SSLOptions(**opts) + cls.d = test.Daemon( + staticdir=test_data.path("data"), + anchors=[ + (re.compile("/anchor/.*"), "202:da") + ], + ssl=cls.ssl, + ssloptions=so, + sizelimit=1 * 1024 * 1024, + noweb=cls.noweb, + noapi=cls.noapi, + nohang=cls.nohang, + timeout=cls.timeout, + hexdump=cls.hexdump, + nocraft=cls.nocraft, + logreq=True, + logresp=True, + explain=True + ) + + @classmethod + def teardown_class(cls): + cls.d.shutdown() + shutil.rmtree(cls.confdir) + + def teardown(self): + if not (self.noweb or self.noapi): + self.d.clear_log() + + def getpath(self, path, params=None): + scheme = "https" if self.ssl else "http" + resp = requests.get( + "%s://localhost:%s/%s" % ( + scheme, + self.d.port, + path + ), + verify=False, + params=params + ) + return resp + + def get(self, spec): + resp = requests.get(self.d.p(spec), verify=False) + return resp + + def pathoc( + self, + specs, + timeout=None, + connect_to=None, + ssl=None, + ws_read_limit=None, + use_http2=False, + ): + """ + Returns a (messages, text log) tuple. + """ + if ssl is None: + ssl = self.ssl + logfp = cStringIO.StringIO() + c = pathoc.Pathoc( + ("localhost", self.d.port), + ssl=ssl, + ws_read_limit=ws_read_limit, + timeout=timeout, + fp=logfp, + use_http2=use_http2, + ) + c.connect(connect_to) + ret = [] + for i in specs: + resp = c.request(i) + if resp: + ret.append(resp) + for frm in c.wait(): + ret.append(frm) + c.stop() + return ret, logfp.getvalue() + + +tmpdir = netlib.tutils.tmpdir + +raises = netlib.tutils.raises + +test_data = utils.Data(__name__) + + +def render(r, settings=language.Settings()): + r = r.resolve(settings) + s = cStringIO.StringIO() + assert language.serve(r, s, settings) + return s.getvalue() |