diff --git a/pathod/.appveyor.yml b/pathod/.appveyor.yml new file mode 100644 index 000000000..dbb6d2fa6 --- /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 000000000..7e9782940 --- /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 000000000..69ac3f052 --- /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 000000000..7eeeb6142 --- /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 000000000..725c15ad9 --- /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 000000000..9a3b615f7 --- /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 000000000..f9e4b8278 --- /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 000000000..1c8b1d695 --- /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 000000000..75ff5be6b --- /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 000000000..92634df7f --- /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 000000000..2de445b44 --- /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 000000000..ab76a35c4 --- /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 000000000..908aa13f7 --- /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 000000000..f492b0efd --- /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 000000000..9f8c487be --- /dev/null +++ b/pathod/README.mkd @@ -0,0 +1,44 @@ +[![Build Status](https://img.shields.io/travis/mitmproxy/pathod/master.svg)](https://travis-ci.org/mitmproxy/pathod) +[![Code Health](https://landscape.io/github/mitmproxy/pathod/master/landscape.svg?style=flat)](https://landscape.io/github/mitmproxy/pathod/master) +[![Coverage Status](https://img.shields.io/coveralls/mitmproxy/pathod/master.svg)](https://coveralls.io/r/mitmproxy/pathod) +[![Downloads](https://img.shields.io/pypi/dm/pathod.svg?color=orange)](https://pypi.python.org/pypi/pathod) +[![Latest Version](https://img.shields.io/pypi/v/pathod.svg)](https://pypi.python.org/pypi/pathod) +[![Supported Python versions](https://img.shields.io/pypi/pyversions/pathod.svg)](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 000000000..f8fbdac59 --- /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 000000000..cf94151b2 --- /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 000000000..7c0386c1f --- /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 000000000..6085c98aa --- /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 000000000..f0ba5844b --- /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 000000000..e69de29bb diff --git a/pathod/libpathod/app.py b/pathod/libpathod/app.py new file mode 100644 index 000000000..debebaf2d --- /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/') + 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 000000000..32199e089 --- /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 000000000..34a9bafb5 --- /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 000000000..a43029980 --- /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 000000000..84ad3c02e --- /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 000000000..a17e7052f --- /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 000000000..a82f12fe3 --- /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 000000000..d5e3ca311 --- /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: + ::
: + e.g.: + GET:/ + GET:/:h"foo"="bar" + POST:/:h"foo"="bar":b'content body payload' + + Normal HTTP responses: + :
: + e.g.: + 200 + 302:h"foo"="bar" + 404:h"foo"="bar":b'content body payload' + + Individual HTTP/2 frames: + h2f::::: + 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 000000000..331248565 --- /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 000000000..ea7c870e5 --- /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 000000000..1a27e1efd --- /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 000000000..f203542f9 --- /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 000000000..55c2a6e07 --- /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 000000000..bf827a9aa --- /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 000000000..55e75074f --- /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 000000000..c92722494 --- /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 000000000..1a8c7dabb --- /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 000000000..1f1765cb8 --- /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 000000000..a098a14e6 --- /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 000000000..134d27bcb --- /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 000000000..2e79d91a5 --- /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 000000000..143569819 --- /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('